eddev
Design System

Responsive Scaling

Configure responsive Tailwind tokens with the local helper.

Responsive scaling is configured in tailwind.config.ts through the local tailwind-helper. Current projects still use Tailwind v3, so the helper config is the source of truth for Tailwind screens, design token values, and how those values move between screens.

The usual pattern is to author real design values at BASE and one key desktop breakpoint, then let the helper fill the intermediate and wide-screen values. That keeps components on stable classes such as p-8, gap-grid-gutter, rounded-lg, and type-title-xl, while the generated CSS adjusts the actual size continuously as the viewport changes.

Standard Config

responsiveTheme({
  screens: { sm: 600, md: 900, lg: 1200, xl: 1600, xxl: 2000 },
  breakpoints: ["lg"],
  interpolate: {
    sm: {},
    md: { base: "sm", lerp: true },
    lg: { base: "lg", lerp: true },
    xl: { base: "lg", scale: 1.2, lerp: true },
    xxl: { base: "lg", scale: 1.3 },
  },
  spacing: {
    BASE: {
      4: 12,
      8: 32,
    },
    lg: {
      4: 16,
      8: 56,
    },
  },
})

screens defines the normal Tailwind min-width breakpoints. BASE is not a Tailwind screen; it is the value before any screen media query applies.

breakpoints lists the screens that need explicit token maps. With breakpoints: ["lg"], token families such as spacing, size, borderRadius, and typography.styles normally define BASE and lg. The other screens are generated from those anchors.

interpolate describes how each screen gets its value:

EntryMeaning
sm: {}Carry the current value at 600px. This is usually still the BASE value.
md: { base: "sm", lerp: true }Start a smooth ramp at 900px, from the sm value toward the next screen value.
lg: { base: "lg", lerp: true }Use the authored lg value at 1200px, then ramp toward xl.
xl: { base: "lg", scale: 1.2, lerp: true }Use the lg value scaled up for 1600px, then ramp toward xxl.
xxl: { base: "lg", scale: 1.3 }Use the lg value scaled up for 2000px and above.

lerp applies to the segment that starts at that screen. For example, md starts the 900px -> 1200px ramp, and lg starts the 1200px -> 1600px ramp.

How Values Are Generated

For each token, the helper walks the screens in order:

  1. Read the current screen's base.
  2. If that base has an authored token value, use it.
  3. If it does not, carry the previously resolved value.
  4. Apply scale when present.
  5. If lerp is true, emit a fluid value that reaches the next screen's value at the next screen width.

Using the spacing.8 example above, the generated shape is:

:root {
  --spacing-8: 32px;
}

@media (min-width: 900px) {
  :root {
    --spacing-8: calc(32px + (100vw - 900px) / 300 * 24);
  }
}

@media (min-width: 1200px) {
  :root {
    --spacing-8: calc(56px + (100vw - 1200px) / 400 * 11.2);
  }
}

@media (min-width: 1600px) {
  :root {
    --spacing-8: calc(67.2px + (100vw - 1600px) / 400 * 5.6);
  }
}

@media (min-width: 2000px) {
  :root {
    --spacing-8: 72.8px;
  }
}

There is no sudden jump at 900px, 1200px, or 1600px: each ramp starts at the value already on screen and ends at the value the next media query starts with.

Smooth Versus Stepped

Without interpolation, the site keeps one value until the next breakpoint and then snaps to the new value. With interpolation, the value changes gradually across the viewport range.

0smmdlgxlxxlbaselg1.2x1.3xinterpolatedstepped

The interpolated line reaches the same anchor values as the stepped line, but it travels between them instead of snapping at each breakpoint.

Why This Works Well

Most designs have a strong mobile composition and a strong desktop composition. The awkward part is everything between them. If spacing, control sizes, radii, and type all jump at a single breakpoint, the page can feel like it rebuilds itself while the browser is being resized.

The helper avoids that by treating breakpoints as anchors, not cliffs. The BASE and lg values still preserve the design decisions. The interpolation config simply describes how to travel between those decisions, and the wide screen scale values keep large displays from feeling like the same desktop layout stretched across too much space.

Because the same interpolation map is used by spacing, size, radius, and typography tokens, their relationships stay coordinated. A card's padding, its gap, its corner radius, and the heading inside it can all resize through the same viewport ranges without each one inventing a separate breakpoint behavior. Grid gutters and margins can join the same rhythm by referencing spacing variables in the grid config.

BASEmdlgxlxxlspacing.4spacing.8title size

Different tokens can have different authored values, but they still move through the same breakpoint rhythm.

Practical Rules

  • Keep screens in ascending order and treat names like md or lg as layout ranges, not device names.
  • Keep breakpoints short. Most sites only need BASE and lg token maps.
  • Put actual design decisions in BASE and each key breakpoint. Use interpolation for the in-between screens.
  • Use scale for wide screens when a project should breathe more at xl and xxl; omit it when the desktop composition should stay fixed.
  • For grid gutters and margins, reference spacing variables when they should scale with the token system.
  • Prefer token classes in components. If a value should scale with the system, add it to spacing, size, borderRadius, or typography.styles instead of hard-coding a one-off responsive value.

On this page