The power of responsive variants in components
How to use responsive variants in components to create a more dynamic and flexible design system.
The problem with responsive design in components
When you want to create components that can be applied with a maximum of flexibility, you often end up with a lot of different variants. For example, if you want to create a button component, you might end up with a lot of different props, depending on the color, size, shape, etc.
While this already allows a lot of flexibility, it all goes down the drain very fast, when you want to apply different variants for different screen sizes. One way would be to hardcode different media queries into the component and define the behavior for each screen size centrally. However, this approach has the downside, that you remove the flexibility for the consumer to choose the variant they need.
Another way to handle this is to use some kind of media query hook, that can be used to dynamically change the props of the component based on the screen size. But this comes with certain drawbacks:
- First, you need an additional dependecy, that might not be available in the project.
- Second, you need to make sure, that the hook is used in all places, where the component is rendered, otherwise, it might not behave as expected.
- Third, you need to make sure, that the hook is not causing unnecessary re-renders, as this can lead to performance issues.
- Fourth, it does not work in an environment, where you can't access the window object, like in server rendering. In this scenario, you would need to define a fallback szenario and the props could change again after the initial hydration, causing large layout shifts.
So it is obvious, that we need a solution that allows us to define the variants centrally, but let the consumer decide on which variant to use for each breakpoint using media queries.
The solution: Responsive variants with responsive-class-variants
Responsive Class Variants (Update: Feb 2026)
Based on this needs I have built a library called responsive-class-variants that allows us to define responsive variants in a component.
First we need to define a type, that allows us to define a value for each breakpoint or just a value for all breakpoints. This way, we can define the variants centrally and let the consumer decide on which variant to use for each breakpoint.
import type { ResponsiveValue } from 'responsive-class-variants'
type ButtonSize = ResponsiveValue<'sm' | 'md' | 'lg'>
type ButtonColor = ResponsiveValue<'primary' | 'secondary' | 'danger'>
type ButtonProps = {
size: ButtonSize
color: ButtonColor
} & HTMLAttributes<HTMLButtonElement>
The ResponsiveValue type is a utility type that allows us to define a value for each breakpoint or just a value for all breakpoints. With this, we have our component API ready. Let's have a look at how this will look like for the consumer.
<Button
size={{ initial: 'sm', sm: 'md', md: 'lg' }}
color={{ initial: 'primary', sm: 'secondary', md: 'danger' }}
>
Hello Responsive World
</Button>
We are now able to specify exactly what we need for each breakpoint and variant. In this case, the initial breakpoint the button will be sm and the color will be primary. On md and wider screens, the button will be md and the color will be secondary, and on the lg breakpoint and wider, the button will be lg and the color will still be primary.
Handling the responsive variants in the component
I have always been a big fan of the variants approach, that was popularized by libraries like class-variance-authority (cva). However, it was not possible to use this approach with responsive variants. With responsive-class-variants, we can now use the same approach to handle responsive variants.
import { rcv } from 'responsive-class-variants'
import type { ButtonProps } from './button'
const buttonStyles = rcv({
base: "btn",
variants: {
size: {
sm: "btn--sm",
md: "btn--md",
lg: "btn--lg",
},
color: {
primary: "btn--primary",
secondary: "btn--secondary",
danger: "btn--danger",
},
disabled: {
true: "btn--disabled",
},
},
compoundVariants: [
{
size: "sm",
color: "primary",
disabled: true,
className: "btn--special",
},
],
});
export const Button = ({
size,
color,
className,
disabled,
...props
}: ButtonProps) => {
return (
<button
{...props}
className={buttonStyles({ size, color, disabled, className })}
disabled={disabled}
/>
);
};
The rcv function offers a similar API to the cva function, meaning you can define base styles, variants and compound variants. On top of that, it handles the creation of the responsive classes for you. When ever you define a variant that supports a responsive value, it will automatically prefix the className with the breakpoint.
You can read more about the library and how to use it in the README.
You can see the full implementation here in this code sandbox:
Conclusion
With this approach, we can now create components, that are fully responsive and can be used with a maximum of flexibility. We can define the variants centrally and let the consumer decide on which variant to use for each breakpoint. The implementation is not that different from the normal variants, but it adds a lot of flexibility and allows us to create more dynamic and flexible design systems.