Handling Responsive Variants in UI Components

Handling responsive variants inside UI components requires balancing clean developer ergonomics, rendering performance, and maintainability. This article explores the most effective strategies for managing responsive component states—contrasting CSS-first styling, object-based responsive props, and modern container queries—to help you build modular, fluid UI elements without introducing layout shifts or unnecessary JavaScript runtime overhead.

1. Adopt Modern CSS Container Queries

The modern standard for building truly reusable, responsive components is CSS container queries (@container). While traditional viewport-based media queries look at the entire browser window, container queries evaluate the dimensions of the component’s immediate parent element.

Using container queries decouples your component from page-level layouts. For example, a card component placed in a narrow sidebar should render its compact variant, while the same component placed in a wide main content area should render its expanded variant.

To implement this:

  1. Define the parent container context: container-type: inline-size;.
  2. Apply styles conditionally within the component using @container (min-width: 400px) { ... }.

This eliminates the need to manually pass breakpoint props down multiple component layers.

2. Use Responsive Object Props with CSS Variables

If your application relies on a component library or design system (such as Chakra UI, Mantine, or a custom system), the cleanest developer interface is the responsive prop pattern using object syntax:

<Button size={{ base: 'small', md: 'medium', lg: 'large' }} />

To handle this cleanly under the hood without causing layout shifts:

  • Map these prop keys directly to pre-generated atomic CSS classes or CSS custom properties (variables).
  • Avoid computing variants using client-side JavaScript calculations.
  • Use a mobile-first resolution strategy where base acts as the default fallback.

By transforming responsive props into CSS custom properties, the browser handles the styling updates natively via CSS media queries, keeping the JavaScript bundle minimal.

3. Leverage Utility-First Responsive Modifiers

When working with utility-first frameworks like Tailwind CSS, avoid creating multi-variant JavaScript props for simple layout or sizing shifts. Instead, expose class composition through utility modifiers:

// Prefer composition
<Card className="p-4 md:p-6 lg:p-8" />

For variants built with libraries like Class Variance Authority (CVA), keep responsive rules inside class strings rather than multiplying variant definitions in TypeScript. This prevents variant explosion and keeps the component API simple.

Anti-Patterns to Avoid

  • JavaScript Viewport Listeners (useMediaQuery, window.innerWidth): Using client-side JavaScript hooks to conditionally render different JSX structures (e.g., {isMobile ? <MobileNav /> : <DesktopNav />}) leads to layout shifts and breaks Server-Side Rendering (SSR) through hydration mismatches. Reserve JavaScript media queries solely for mounting behaviors where DOM nodes cannot exist in the tree simultaneously (such as a native modal vs. a dropdown).
  • Hardcoded Viewport Assumptions: Avoid coupling a component's internal variant directly to fixed global screen sizes when the component is intended to sit inside flexible grid systems.

The Best Approach

For isolated, reusable design system components, CSS container queries combined with standard CSS variables represent the best modern practice. When ergonomic developer APIs are required across a team, responsive props mapped to static CSS rules provide the ideal balance between developer experience and browser performance.