Polymorphic Styling with styled‑components: Keep Your Styles While Changing Elements
Need button styles on an anchor? styled‑components’ polymorphic `as`/`component` prop lets you swap underlying elements without duplicating CSS. This guide covers TypeScript typing, ref forwarding, SSR, and a concrete example.
17 Nov 2025, 05:29 UTC

Problem: Reusing Styles Across Semantic Elements
In a typical React project you might design a Button component with a specific look‑and‑feel. Later you discover you need the same visual treatment on a link, a div, or a custom component. Re‑creating the same CSS for each element is error‑prone and bloats the stylesheet. The question is: can we keep the style once and just change the rendered tag?
Thesis: The Polymorphic as/component Prop Is the Answer – With Caveats
Styled‑components introduced the as prop (v5) and renamed it to component in v6, but kept as as an alias for backward compatibility. When you pass {"as": "a"} or {"component": "a"}, the library renders an <a> element while applying the same generated class name that would have been used for a <button>. The core idea is simple: a single styled definition can morph into any element you need, preserving styles and class names.
1. Polymorphic Rendering in Practice
Below is a minimal example that demonstrates the API. The code is intended to run in a React + TypeScript environment with styled‑components v6.
import styled from "styled-components";
// A button styled with blue background and white text
const Button = styled.button`
background: #007bff;
color: #fff;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
`;
// Usage: render as a normal button
<Button>Click me</Button>
// Render as an anchor – the same styles apply
<Button as="a" href="/home">Home</Button>
When the component renders, styled‑components injects a unique class name (e.g., .sc-abc123) into the className prop of whichever element is chosen. The CSS for that class remains the same regardless of the underlying tag.
2. TypeScript Typing Patterns
Polymorphic components break the usual “props of the underlying element” rule. To get correct type checking you need to tell TypeScript what the base element is and allow it to infer the props of the target when as changes.
import { ComponentPropsWithoutRef } from "react";
import styled from "styled-components";
// Base type: a button with no ref forwarded
export type ButtonProps = ComponentPropsWithoutRef<"button">;
const Button = styled.button`
/* styles */
`;
// Use the component
<Button as="a" href="/home" />
In v6 the prop is called component, but the typing logic is identical. If you use a custom component as the target, you must explicitly forward the ref so that ref points to the underlying DOM node rather than the styled wrapper.
3. Practical Checklist for a Polymorphic Component
- Define the base props with
ComponentPropsWithoutRef<T>orComponentPropsWithRef<T>whereTis the default element. - Use the generic type on the styled call:
styled<T, Props>(element). - When rendering, pass
as="tag"(orcomponent="tag") and any necessary attributes for that tag (e.g.,hreffor anchors). - Test the component with
@testing-library/reactby rendering it with differentasvalues and asserting the resulting DOM tag usingscreen.getByRoleorcontainer.querySelector. - Verify the generated class name is the same across all variants by inspecting the element in React DevTools.
- If you need a ref, wrap the styled component in
React.forwardRefand forward it to the underlying element. - For SSR, ensure the same
asvalue is used on both the server and client renders to avoid hydration mismatches.
4. Trade‑offs and Limitations
| Aspect | What to Watch For |
|---|---|
| Ref Forwarding | Only works automatically with native elements or components that forward refs. Custom components need manual forwardRef. |
| Accessibility | Changing the semantic element can remove native behavior (e.g., <button> is keyboard‑focusable). You must add role or tabIndex if needed. |
| SSR Hydration | Differing as values between server and client cause mismatched markup. Keep the prop stable. |
| Performance | Each unique as value creates a new internal styled component class. In large lists with many dynamic tags, this can increase style recalculation. |
| Specificity | Chaining styled(StyledComponent) with as may produce unexpected specificity due to class name ordering. |
5. When to Use Polymorphic vs. Composition
If you only need to switch between a handful of semantic tags (e.g., button, a, div), the as prop is convenient. If you’re building a library where consumers might pass arbitrary custom components, consider composing styles instead: styled(OtherStyledComponent)`…`. This avoids prop forwarding complexity but sacrifices element flexibility.
Conclusion: Harness the Power, Mind the Pitfalls
The polymorphic as/component prop is a powerful feature that lets you write a single styled component and render it as any element you need. By correctly typing the base props, forwarding refs, and testing for accessibility and SSR consistency, you can keep your CSS DRY while maintaining semantic HTML. Follow the checklist above to implement a robust polymorphic component in your codebase.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.