skip to content

You own the shared CSS for a design system used by many teams: how do you decide how many breakpoints it defines, and what does each additional one cost?

level: principalimportance: should knowfreq 33%

answer

  1. breakpoints multiply across the inventory
  2. a shared breakpoint is a long-lived interface
  3. var() is illegal in a media condition
  4. prefer continuous layouts over discrete jumps
  5. local query beats a new global one

basics

~20 s

Keep the shared set small — typically three or four — because every breakpoint multiplies across the whole component inventory as states to design, review, document and test. Local one-off adjustments belong inside a component, not in the global scale.

solid answer

~50 s

A shared breakpoint is not one number; it is a state that every component in the inventory may need designed, reviewed, documented, screenshot-tested and supported. That cost is multiplicative, so I hold the shared scale to three or four values and treat additions as an architectural change with a written case. When a team asks for a fifth, the question I ask is whether their layout genuinely rearranges at that width or merely resizes — resizing is usually better absorbed by wrapping layouts and fluid spacing, which need no breakpoint at all, and a component that truly needs an unusual width can carry a local query without promoting it globally. The mechanical wrinkle is that `var()` is not allowed inside a media query condition and `@custom-media` has not shipped in browsers, so shared breakpoint values live in the build layer or get duplicated — which makes changing one later expensive, and is another reason to add few.

go deeper

for a junior

Know that a design system publishes a small fixed set of breakpoints and that you use those values rather than inventing your own, so components across the product rearrange at the same widths.

for a middle

Explain why breakpoint values cannot be custom properties — var() is not allowed in a media condition — and therefore live in the build layer, which is what makes changing one later a coordinated migration.

for a senior

Argue the multiplicative cost concretely: each breakpoint is a state per component to design, review, document and screenshot-test. Offer the alternatives you would propose instead — continuous layouts, fluid sizing, a local query owned by one component.

for a principal

Own the addition as an interface change with a written case, a review of what it costs the existing inventory, and a follow-through obligation to audit components at the new width. Be able to name the signals that the scale is already too large or genuinely too small.

## Why the count is a governance question, not a taste question One product team choosing four breakpoints is a preference. A shared design system choosing them is a multiplication: **breakpoints × components × states**. If the library holds 60 components and you add a fifth breakpoint, you have created up to 60 new layout states that someone must design, review against the spec, document with an example, capture in visual regression, and support in bug reports for as long as the system lives. Nobody does all of that, which is the real problem — the breakpoint gets added, most components are never checked at it, and the system now makes a promise it does not keep. So the useful framing in an interview is: a shared breakpoint is a **long-lived interface**, not a value. It is easy to add and effectively impossible to remove, because consumers write queries against it the week after it lands. ## The mechanical constraint that shapes the design Custom properties look like the obvious way to name breakpoints, and they do not work for this: ```css :root { --bp-md: 48rem; } /* This does NOT work — var() is not allowed in a media query condition */ @media (min-width: var(--bp-md)) { /* ... */ } ``` Media conditions are evaluated outside the element-based cascade that `var()` depends on, so the value cannot be substituted there. The `@custom-media` rule that would solve this cleanly is a specification proposal that has **not shipped in browsers**; it is available only through build tooling such as a PostCSS plugin. As of 2026, the practical options are all build-layer: preprocessor variables, generated mixins, or a token pipeline that emits the literal values. That has a direct governance consequence. Because the values are baked at build time and consumers may also hard-code them, **changing a breakpoint later is a coordinated migration**, not a token edit. Add few, choose them carefully, and expect them to outlive the design that motivated them. ## What a good shared scale looks like Three or four values, derived from where the *system's own* layout primitives break rather than from device names, spaced widely enough that each one produces a visibly different arrangement. Publish them with a stated intent — "sm: single column gains a second", "lg: the sidebar appears" — so consumers can tell whether their case belongs at an existing breakpoint or genuinely elsewhere. A named intent also gives you the vocabulary to reject requests: if someone wants a breakpoint at 830px, the conversation is about which intent it serves, not about a number. ## The escape hatches that keep the scale small Most requests for another shared breakpoint are better served three other ways: 1. **Layouts that absorb size change continuously.** Wrapping rows and flexible track sizing let a layout rearrange itself over a range of widths without any discrete jump. Every breakpoint avoided this way is one that never needs designing or testing. 2. **Fluid sizing for the things that merely scale.** Spacing and type that interpolate with viewport width remove the class of "breakpoint" that exists only to bump a value. 3. **Local queries inside one component.** A component with a genuinely unusual threshold can carry its own query. It is a documented exception with one owner, rather than a fifth promise made to sixty components. There is a fourth, more structural answer worth naming: a component that reacts to *its own container* rather than the page is not asking for a page-level breakpoint at all, and that mechanism belongs to a different part of the system. ## How to run the decision Treat additions like an API change. A written case naming the layouts that break, the widths where they break, and why an existing breakpoint or an escape hatch does not serve. A review that asks what the addition costs the existing inventory. And, if approved, an obligation to actually check the inventory at the new width rather than declaring it supported. If that obligation feels too heavy for the request, the request was not worth a shared breakpoint. ## Signals you got it wrong A scale is too large when consumers cannot say what a given breakpoint is *for*, when several breakpoints produce visually identical output for most components, or when new components pick breakpoints by copying whichever nearby component looked similar. It is too small when teams routinely add local queries at roughly the same unofficial width — that convergence is real evidence, and it is the one case where promoting a value to the shared scale is clearly right. ## What the interviewer wants to hear Not a number. The multiplication argument, the awareness that the value is baked at build time and therefore expensive to change, the escape hatches you would offer instead, and a decision process that puts the burden of proof on the addition.

  • Why can't shared breakpoint values simply be custom properties?
    Because `var()` is not permitted inside a media query condition — the condition is evaluated outside the element-based cascade that custom property substitution depends on, so there is nothing to resolve against. The `@custom-media` rule that would fix this is a proposal and has not shipped in browsers, so it only works via build tooling like PostCSS. In practice the values are resolved at build time by a preprocessor or a token pipeline, which is precisely why changing one later is a migration rather than an edit.
  • A team asks for a fifth shared breakpoint at 830px. How do you handle it?
    I ask what happens at 830px. If the layout genuinely rearranges — a column appears, a nav changes shape — the next question is whether an existing breakpoint could carry it with a small design compromise. If it only resizes, wrapping layouts or fluid spacing handle it with no breakpoint. If it is real and specific to their component, a local query inside that component is the right answer: one owner, documented as an exception, rather than a new promise made to every component in the library.
  • When is adding a shared breakpoint clearly the right call?
    When several independent teams have already added local queries at roughly the same width. That convergence is evidence rather than opinion — it means the system's layout primitives genuinely break there and everyone is paying separately to work around it. Promoting the value then removes duplication instead of creating new obligations. I would still pair the addition with an actual pass over the existing inventory at that width, because an unchecked breakpoint is a promise the system does not keep.

saying these in an interview costs you the question

  • Names a fixed breakpoint count without asking about the components
  • Assumes var() works inside a media query condition
  • Treats adding a breakpoint as free because it is one line
  • Adds shared breakpoints to satisfy a single component's need
  • Declares a new breakpoint supported without auditing existing components

context