React Bootstrap's `as` Prop: Polymorphic Components Without Wrapper Hell
React Bootstrap's `as` prop lets any component render as a different HTML element or custom component — preserving styling, refs, and accessibility. Learn the pattern, TypeScript typing, and the gotchas that bite when wrapping router links or custom components.
22 Aug 2025, 11:57 UTC

The Wrapper Div Problem
You need a Bootstrap-styled button that navigates like a link. The naive approach wraps a <Link> around a <Button> or vice versa, producing invalid HTML (<a> inside <button> or a <div> sandwich) and breaking accessibility. React Bootstrap's as prop solves this by letting any component render as a different element or custom component while preserving styling, refs, and ARIA attributes — no wrapper divs required.
How the as Prop Works
Nearly every React Bootstrap component (Button, Navbar, Dropdown, Alert, etc.) accepts an as prop. Pass a string HTML tag ("div", "span", "button") or a React component type (your own component, react-router's Link, Next.js Link). The component factory uses React.forwardRef and a shared helper (createWithBsPrefix / useBootstrapComponent) to clone the target element with merged className, style, ref, and all remaining props. This logic has been stable since v2.0 (2021).
Worked Example: Router Link Styled as a Button
In a React Router v6 app, replace the wrapper pattern with a single polymorphic component:
import { Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
function DashboardCTA() {
return (
<Button as={Link} to="/dashboard" variant="primary">
Go to Dashboard
</Button>
);
}This renders an <a href="/dashboard"> with Bootstrap's .btn.btn-primary classes, proper focus styles, and keyboard activation. The to prop passes through to Link; onClick handlers on Button override Link's internal handler (test this if you attach analytics). TypeScript infers the correct prop union: ButtonProps & LinkProps minus conflicting keys.
TypeScript Polymorphic Typing
The typings use ComponentPropsWithRef<ElementType> and ElementType so that as="button" yields native button props (disabled, type), as={Link} yields Link props (to, replace), and custom components receive their own prop types merged with Bootstrap's base props. If you write a custom CardAction component, annotate it with React.ForwardRefExoticComponent<CardActionProps & React.RefAttributes<HTMLDivElement>> to keep IntelliSense working.
Trade-offs and Gotchas
- Custom component contract: Any component passed to
asmust acceptclassName,style, andref(viaforwardRef). Missing any of these breaks styling, ref forwarding, or prop spreading silently. - Event handler precedence: Props on the React Bootstrap component (
onClick) override same-named props on theascomponent. Verify interaction behavior when wrapping router links. - SSR compatibility: Custom
ascomponents must be SSR-safe. Avoid browser-only APIs (e.g.,window,IntersectionObserver) in components passed toaswhen using Next.js or Remix. - Internal state machines unchanged:
Dropdowntoggle logic stays bound to the rendered element's click events. Ensure the target element supports expected interactions (e.g., don't render a dropdown trigger as a non-focusable<div>). - Memoization impact: Frequently changing
asvalues (dynamic element types) defeatReact.memobecause the element type changes. Keepasstable per render path.
Verify It Works
Run a quick accessibility audit on the rendered output. In a dev build, open Chrome DevTools → Lighthouse → Accessibility and confirm the polymorphic button scores 100 on keyboard focus and ARIA attributes. For SSR, build a Next.js 14 page using <Navbar.Brand as={Link} href="/"> and run npm run build — no hydration mismatch warnings means the server and client output match.
When to Reach for as
Use as when you need semantic HTML (anchor for navigation, button for actions) with Bootstrap styling, or when composing with router links, custom design-system primitives, or headless UI components. Avoid it for purely presentational changes — CSS classes or the bsPrefix prop are lighter tools. The polymorphic pattern is a core API design decision that keeps your JSX flat, your HTML valid, and your TypeScript types accurate.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.