skip to content

You lead frontend for a long-lived product with several teams contributing. How would you decide between a utility-first system and semantic component classes, and where would you draw the boundary if you allowed both?

level: principalimportance: should knowfreq 32%

answer

  1. decide from the codebase, not from taste
  2. is the markup already componentized?
  3. who actually edits the styling
  4. hybrid is fine, an unstated boundary is not
  5. freeze the old stylesheet, migrate leaf-first

basics

~20 s

Decide on the codebase's shape, not taste: utility-first pays off only where a componentized markup layer already collapses duplication. If both are allowed, split by responsibility — utilities compose inside components, hand-written CSS owns base layers, structural features and third-party overrides — and enforce the boundary explicitly.

solid answer

~60 s

I would start from observable facts rather than preference. Is the markup componentized, so a class string can live in one place? Who edits styling — only frontend engineers, or also backend engineers and content authors? How much of the UI is repeating application patterns versus bespoke art direction? How much does shared styling change under how many hands? Utility-first pays where markup is componentized, patterns repeat, and many people touch the CSS, because it converts risky global edits into bounded local ones. Semantic classes pay where markup is hand-written or authored outside the codebase, and where design is bespoke. Most real products end up hybrid, and the failure there is having two systems with no rule about which is used when. So I would write the boundary down: utilities for composition inside components, hand-written rules for the reset and base typography, for things utilities express badly, and for third-party markup we cannot edit — with cascade layers ordering the two so precedence is not an accident. Then I would migrate leaf-first rather than big-bang, and accept a long coexistence.

go deeper

for a junior

Know that this is a codebase-level decision rather than a personal preference, and that most real products end up using both models for different parts of the UI.

for a middle

Be able to name the properties that decide it — whether markup is componentized, how repetitive the UI is, who edits styling — instead of arguing readability.

for a senior

Show that you would draw and document the boundary between the two, order them deliberately with cascade layers, and migrate incrementally rather than rewriting.

for a principal

Own the costs you are choosing: onboarding to a dialect, portability, review workflow, and vendor coupling — plus the evidence you would watch to know the call was wrong.

## Decide from facts about the codebase This question invites an opinion; give it evidence instead. Four properties of the codebase predict the answer better than any argument about readability. **1. Is there a markup abstraction layer?** Utility-first's duplication is collapsed by templates or components, not by CSS. If the product renders through reusable units, repeating a class string costs nothing because it is written once. If markup is hand-written pages, CMS content, or email templates, that collapse never happens and the duplication is real. This is the single strongest predictor. **2. Who edits styling?** If only a small frontend group touches CSS and they know the whole stylesheet, a semantic system is manageable — the tacit knowledge exists. If backend engineers, contractors and new joiners all ship UI, nobody holds that map, and a system where styling is read and changed locally scales better socially. **3. What kind of UI is it?** Application UI is repeating patterns: tables, forms, dialogs, lists. Marketing and editorial work is bespoke by design. A bounded utility vocabulary is close to free in the first case and constant friction in the second. **4. How volatile is shared styling?** Measure it: how often do shared rules change, and how often does one of those changes cause a regression somewhere unintended? A high rate is direct evidence that unbounded blast radius is costing you, which is the concrete argument for moving styling local. ## Draw the boundary if you allow both Hybrids are normal and are not the failure. The failure is an unstated boundary, where two systems overlap and every developer guesses, so the same button exists as both a class and a class string and nobody knows which wins. Write the rule down as a short, checkable policy. A boundary that holds up in practice: - **Utilities**: composition inside components — spacing, layout, colour, type scale, states. The default for everyday work. - **Hand-written CSS**: the reset and base element defaults; typography defaults for long-form content the markup does not control; anything structurally awkward as utilities, such as a multi-line `grid-template-areas`, `@keyframes`, complex pseudo-element treatments, or print rules; and overrides for third-party markup you cannot add classes to. - **Precedence**: order the two with `@layer` so which one wins is a declared decision rather than an artefact of bundle order. Note that cascade-layer support is now broadly available in current browsers, but confirm against your own support matrix. Make the rule reviewable. "Did this PR add a component class for something the utility vocabulary already covers?" is a question a reviewer can actually answer; "is this idiomatic?" is not. ## Migration, if you are changing an existing system Do not big-bang. A rewrite of a large stylesheet is a long project with no user-visible outcome, and it competes with feature work it will lose to. Instead: - Adopt the new model for **new** surfaces only, so the old stylesheet stops growing. Freezing growth is most of the benefit and costs nothing. - Convert **leaf** components — things nothing else depends on — opportunistically as you touch them. - Track the old stylesheet's size as a one-way ratchet: it may shrink, never grow. That single rule prevents the classic outcome of two systems both expanding. - Accept a coexistence measured in years, and say so up front. Teams that promise a six-month migration create a credibility problem when it takes three times that. ## Costs to state honestly A principal-level answer names what the choice costs, not just what it buys. - **Onboarding.** A utility vocabulary is a dialect: people who know CSS still have to learn the names. That is real ramp-up, offset by the fact that they no longer have to learn *your specific stylesheet*. - **Portability.** Utility markup is coupled to the vocabulary that defines it. Moving markup between codebases, or handing it to a team using a different system, is more work than moving markup with semantic classes. - **Review noise.** Styling changes show up inside markup diffs, which some review workflows handle badly. - **Vendor and ecosystem coupling.** If the vocabulary comes from a tool, you have taken a dependency with its own upgrade cadence. Weigh that like any other dependency. ## The framing that lands Say explicitly that these are two placements of the same information, that the placement should follow the shape of the codebase and the team, and that the worst outcome is not picking the "wrong" one but running both without a boundary. A leader is judged here on whether they can make a defensible call with stated costs and a migration that does not stall delivery — not on which model they prefer.

  • What evidence would actually change your mind after the decision is made?
    The rate of styling regressions traced to shared-rule edits, the old stylesheet's size trend, how often engineers reach for escape hatches or `!important`, and how long new joiners take to ship a UI change. If those trend the wrong way for two quarters, the decision or the boundary is wrong and should be revisited explicitly.
  • Two teams in the same product disagree and want different models. How do you handle it?
    Let the boundary be by surface, not by preference: a marketing site and an application can reasonably differ, since their UI shapes differ. What I would not allow is two models inside one surface with no rule, because then every component has two possible homes and reviewers cannot judge. Divergence needs a stated seam.
  • What is the risk of a bespoke in-house utility vocabulary rather than adopting an existing one?
    You own it forever — documentation, naming consistency, the scale, and every gap someone hits at 6pm. It also loses the onboarding benefit of a vocabulary people already know. It is justified when your design language genuinely does not fit an existing scale, and rarely otherwise.

saying these in an interview costs you the question

  • Argues from personal taste with no codebase evidence
  • Proposes a big-bang stylesheet rewrite
  • Allows both models with no written boundary
  • Ignores who besides frontend engineers edits styling
  • Claims one model is correct for every product

context