Using styled‑components’ attrs to enforce static props on DOM elements
The <code>attrs</code> helper in styled‑components lets you attach static or dynamic attributes to the underlying DOM node. This guide shows how to use it, gives a concrete Button example, explains its limits, and lists common mistakes to avoid.
19 Sept 2026, 09:04 UTC

Why attrs matters
When you build reusable UI components with styled-components, you often want to guarantee that certain HTML attributes are always present. For example, a button should default to type="button" to avoid accidental form submissions, or you might need to add ARIA labels for accessibility. The attrs helper lets you attach these attributes automatically, without burdening every consumer of the component.
How attrs works
The attrs method accepts either a plain object or a function that receives the component’s incoming props and returns an object. The returned key/value pairs are merged into the props that styled-components passes to the underlying DOM element. They don’t influence the styled component’s own styling logic; they are purely for the rendered element.
Because attrs runs at render time, you can base the attributes on the component’s props, but you must be careful to return a new object each call to avoid shared mutable state.
Example: a Button with default type and ARIA
import styled from 'styled-components';
const Button = styled.button.attrs({
type: 'submit',
'aria-label': 'primary action',
})`
background: palevioletred;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
`;
// Usage
// Send
// Renders: <button type="submit" aria-label="primary action" class="…">Send</button>
Notice that the component still accepts normal React props (e.g., onClick or className), and the attrs object is merged with them. If a consumer explicitly passes a type, that value overrides the default from attrs:
<Button type="button">Cancel</Button>
// Renders: <button type="button" …>Cancel</button>
Dynamic attrs with a function
To base attributes on props, supply a function. The function receives the same props that the styled component receives and must return an object.
const IconButton = styled.button.attrs(props => ({
type: props.submit ? 'submit' : 'button',
'aria-label': props.label,
}))`
background: transparent;
border: none;
cursor: pointer;
`;
// <IconButton submit label="Refresh"><Icon/></IconButton>
// Renders: <button type="submit" aria-label="Refresh" …><Icon/></button>
When using a function you must return a fresh object each time. Returning the same mutable object can cause props from one instance to leak into another, leading to subtle bugs.
Limitations you should know
- No effect on styled‑component API:
attrsonly touches the underlying DOM element. If you need default values for props that drive styling (e.g., avariantprop), usedefaultPropsor destructuring with fallbacks. - Non‑DOM targets:
attrsdoes not work with styled components that target non‑DOM elements such asReact.Fragmentor custom components that don’t forward props to a DOM node. - Interaction with the css prop: When using the
cssprop fromstyled-components,attrsstill applies to the DOM node but does not influence the styling logic. Mixing the two can be confusing if you expectattrsto affect CSS. - Overriding props: If a consumer passes a prop that is also defined in
attrs, the consumer’s value wins.attrscannot override explicitly provided props unless you merge them inside the function explicitly.
Common pitfalls and how to avoid them
- Forgot to return an object: When using a function, forgetting
return { … }throws aTypeError. Always wrap the returned object in braces. - Mutating the props object: Some developers mutate the incoming
propsto add attributes. This mutates the same object across all instances, causing cross‑instance leakage. Instead, create a new object. - Assuming attrs attributes are available inside the styled component: The attributes added by
attrsare not part of the component’s props for styling purposes. If you need to read them inside the styled template, you must pass them explicitly as props. - Using attrs with styled(React.Fragment): Since
React.Fragmentdoes not render a DOM node, any attributes added byattrsare silently ignored. Use adivor other wrapper if you need attributes.
How to verify that attrs is working
1. Render in a test environment. Using React Testing Library, query the element and assert the presence of the attribute:
const { getByRole } = render(<Button>Send</Button>);
const button = getByRole('button');
expect(button).toHaveAttribute('type', 'submit');
expect(button).toHaveAttribute('aria-label', 'primary action');
2. Server‑side rendering check. Render to a string and inspect the output:
const html = ReactDOMServer.renderToString(<Button>Send</Button>);
console.log(html); // should contain <button type="submit" aria-label="primary action" …>
These checks confirm that the attributes are present on the DOM node and not just in the component’s internal state.
Practical take‑away
Use attrs when you need to guarantee static or derived attributes on the rendered element—especially for accessibility or HTML compliance. Keep it simple: plain objects for static values, functions for derived values, always return a new object, and remember that attrs does not affect the styled component’s own API.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.