sections/basics/attaching-additional-props.mdx
To avoid unnecessary wrappers that just pass on some props to the rendered component or element, you can use the .attrs constructor. It allows you to attach additional props (or "attributes") to a component.
This way you can for example attach static props to an element or pass a third-party prop like activeClassName to React Router's Link component. Furthermore, you can also attach more dynamic props to a component. The .attrs object also takes functions, that receive the props that the component receives. The return value will be merged into the resulting props as well.
Here we render an Input component and attach some dynamic and static attributes to it:
const Input = styled.input.attrs<{ $size?: string; }>(props => ({
// we can define static props
type: "text",
// or we can define dynamic ones
$size: props.$size || "1em",
}))`
color: #BF4F74;
font-size: 1em;
border: 2px solid #BF4F74;
border-radius: 3px;
/* here we use the dynamically computed prop */
margin: ${props => props.$size};
padding: ${props => props.$size};
`;
render(
<div>
<Input placeholder="A small text input" />
<Input placeholder="A bigger text input" $size="2em" />
</div>
);
As you can see, we get access to our newly created props in the interpolations, and the type attribute is passed down to the element.
Notice that when wrapping styled components, .attrs are applied from the innermost styled component to the outermost styled component.
This allows each wrapper to override nested uses of .attrs, similarly to how CSS properties defined later in a stylesheet override previous declarations.
Input's .attrs are applied first, and then PasswordInput's .attrs:
const Input = styled.input.attrs<{ $size?: string }>((props) => ({
type: 'text',
$size: props.$size || '1em',
}))`
border: 2px solid #bf4f74;
margin: ${(props) => props.$size};
padding: ${(props) => props.$size};
`;
// Input's attrs will be applied first, and then this attrs obj
const PasswordInput = styled(Input).attrs({
type: "password",
})`
// similarly, border will override Input's border
border: 2px solid aqua;
`;
render(
<div>
<Input placeholder="A bigger text input" $size="2em" />
<PasswordInput placeholder="A bigger password input" $size="2em" />
</div>
);
This is why PasswordInput is of a 'password' type, but still uses the $size attribute from Input.