Skip to content
Back to Build Tutorials

Responsive Translation

Translate Figma's responsive variants (mobile/tablet/desktop) into Tailwind breakpoints accurately.

Why

Responsive design is where the figma-to-code handoff loses the most detail. A designer specifies 3 breakpoints in Figma, but the developer implements 2 and guesses the third, or applies a breakpoint at the wrong pixel value. Tailwind's breakpoint system (sm:640, md:768, lg:1024) maps to specific Figma frames, and getting this mapping wrong means the layout breaks at sizes the designer never tested. The disciplined approach is to treat each breakpoint as a separate layout (not just 'smaller padding') and to test at the exact Figma frame widths, not arbitrary browser sizes. This prevents the 'looks fine on my laptop, broken on tablet' class of bug.

When

When the design has ≥2 breakpoints. Takes 1-2 h per screen. Skip for desktop-only internal tools.

How

1. Map Figma frames to Tailwind breakpoints explicitly
   - Mobile (375px) → base (no prefix); Tablet (768px) → md:; Desktop (1440px) → xl:
   - Document this mapping in the design system README

2. Implement mobile-first: write the base (mobile) layout, then add md: and xl: overrides
   - <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3">
   - Never start from desktop and subtract — additive is easier to reason about

3. Use container queries for component-level responsiveness
   - A card should respond to its container width, not the viewport
   - @container card: grid-cols-1 → grid-cols-2 when container > 400px

4. Test at the exact Figma frame widths, not arbitrary sizes
   - Storybook viewport addon: set custom viewports at 375, 768, 1024, 1440
   - Visual regression snapshots at each breakpoint

5. Handle the in-between sizes (414px, 1024px) with fluid units
   - Use clamp() for font sizes that scale smoothly between breakpoints
   - Avoid hard jumps: a heading shouldn't go from 24px to 48px with nothing between

Example

// Mobile-first responsive card grid
export function CardGrid({ cards }: { cards: Card[] }) {
  return (
    <div className="grid grid-cols-1 gap-4 px-4 md:grid-cols-2 md:gap-6 md:px-6 xl:grid-cols-3 xl:px-8">
      {cards.map(c => <Card key={c.id} {...c} />)}
    </div>
  );
}

// Fluid heading with clamp (scales 24px → 48px between 375px and 1440px)
export function Hero({ title }: { title: string }) {
  return <h1 className="text-[clamp(1.5rem,4vw,3rem)] font-bold">{title}</h1>;
}

// Storybook viewports matching Figma frames
export const parameters = {
  viewport: { viewports: {
    mobile: { name: "Mobile", styles: { width: "375px", height: "812px" } },
    tablet: { name: "Tablet", styles: { width: "768px", height: "1024px" } },
    desktop: { name: "Desktop", styles: { width: "1440px", height: "900px" } },
  } },
};

Common Problems

  • ⚠Starting from desktop and using max-width to shrink, which produces broken mobile layouts at unusual sizes.
  • ⚠Using fixed pixel widths (width: 375px) instead of responsive units, so the layout doesn't adapt at all.
  • ⚠Testing only at 3 common sizes (iPhone, iPad, laptop) and missing the 414px Android width where things break.
  • ⚠Applying the same padding at all breakpoints, so mobile has 32px padding eating 17% of a 375px screen.

Best Practices

  • ✓Always implement mobile-first — it forces you to handle the most constrained layout first, where every pixel matters.
  • ✓Use container queries (Tailwind @container) for components that appear in different contexts (sidebar, full-width).
  • ✓Set up a Playwright visual test that screenshots every page at 5 widths (375, 414, 768, 1024, 1440) in CI.
  • ✓Use clamp() for typography and spacing that should scale fluidly, rather than jumping at breakpoints.