React Bootstrap's `as` Prop: Swapping Elements Without Breaking Bootstrap Contracts
React Bootstrap's `as` prop swaps the rendered element while preserving Bootstrap classes, but it does not handle ref forwarding, prop validation, or styling boundaries. Learn how to use it safely.
14 Oct 2026, 18:13 UTC

The navigation button that almost works
You need a button that routes to another page. You reach for React Bootstrap's Button and pass as={Link} from react-router:
<Button as={Link} to="/pricing">See pricing</Button>
The element renders, the btn btn-primary classes appear, and clicking navigates. So far, so good. Then you test keyboard navigation and the focus outline is gone. Or your TypeScript build fails because to is not a valid button prop. Or a CSS rule that targets a direct child of a button group stops matching. The as prop did its job—it swapped the rendered element—but it did not absorb the integration cost. That cost is yours to manage.
What as actually promises
React Bootstrap components are polymorphic. The as prop replaces the intrinsic element (or component) that would normally be rendered while preserving the component's Bootstrap class names and internal behavior. A Button with as="a" still receives btn, btn-primary, and the click handling that Bootstrap expects. A Form.Control with as="select" still receives form-control.
The critical detail is what does not carry over: prop validation, ref plumbing, and DOM attribute filtering. Whatever you pass to as receives every prop you give it, including ones meant for the Bootstrap component. If that component does not forward a ref to a DOM node, React Bootstrap cannot attach event handlers or accessibility attributes, and they disappear silently.
The ref-forwarding contract
Because React Bootstrap attaches behavior to the underlying DOM node, the component you pass to as must forward its ref. In React 18, that means wrapping your component in React.forwardRef. In React 19, refs can be passed as regular props, but the component still needs to apply that ref to a DOM element.
Third-party components are not exempt. If you use as={Link} from react-router, confirm that the installed router version forwards refs. If it does not, the button will look correct but will not receive focus management or Bootstrap's JavaScript behaviors. A quick check is to inspect the rendered DOM: the anchor should carry the btn classes and respond to keyboard activation.
Re-skinning with bsPrefix
Sometimes you want Bootstrap's structure without Bootstrap's look. The bsPrefix prop overrides the base class name. Setting bsPrefix="x-btn" on a Button replaces btn with x-btn in the rendered output. The component's layout logic and behavior still assume the Bootstrap CSS contract, but your stylesheet now owns the visual rules.
<Button bsPrefix="x-btn" variant="primary">Custom</Button>
This is useful for design systems that layer on top of Bootstrap. The trade-off is that you are now responsible for every visual state—hover, active, disabled, focus—that Bootstrap previously provided. Changing bsPrefix changes class names only; it does not alter the component's DOM structure or event handling.
Prop contracts and TypeScript gaps
Prop validity follows the rendered element, not the React Bootstrap component. <Form.Control as="select"> accepts multiple. <Form.Control as="textarea"> accepts rows. Passing the wrong one does not produce a compile error; it produces an invalid DOM attribute or a React warning at runtime.
TypeScript definitions bundled with react-bootstrap do not fully model polymorphic as. When you pass a component whose props differ from the intrinsic element, you will often need a cast. Treat the exact error message as version-specific and check it against your installed package rather than relying on a generic fix.
Worked example: routing button and custom select
The following pattern keeps the DOM flat and preserves Bootstrap's sibling selectors:
import { Button, Form } from 'react-bootstrap';
import { Link } from 'react-router-dom';
// Assumes react-router Link forwards refs in your installed version.
<Button as={Link} to="/checkout">Checkout</Button>
<Form.Control as="select" multiple>
<option>One</option>
<option>Two</option>
</Form.Control>
Compare this with wrapping the button in a <div>. The extra element breaks Bootstrap's sibling selectors in button groups, input groups, and navbars, and it disturbs flex and grid spacing that assumes direct children. Using as avoids that structural cost, but only if the passed component behaves like a proper DOM node.
Practical checklist before shipping
- Run
npm ls react-bootstrapin your terminal to confirm the installed major version, then check that your Bootstrap CSS import matches that major. - Render the component and inspect the DOM: confirm the Bootstrap classes are present and that keyboard activation works.
- If using
bsPrefix, verify your CSS selectors match the new class and that focus and disabled states are styled. - Run your TypeScript build and address
astyping errors locally; do not assume a cast from another project will match your version.
The as prop is a sharp tool. It removes wrapper elements and keeps Bootstrap's class contract intact, but it moves the integration burden onto your component boundaries. Own that boundary, and polymorphic rendering becomes a reliable pattern rather than a silent failure.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.