skip to content

You own frontend standards for an app whose global stylesheet grows every sprint and never shrinks. Which structural conventions and enforcement mechanisms actually stop that decay, and what does each one cost?

level: principalimportance: should knowfreq 30%

answer

  1. adding is cheaper than understanding
  2. nothing fails when a rule dies
  3. make deletion a side effect
  4. unchecked conventions decay
  5. watch what grows next to the gate

basics

~20 s

Stylesheets grow because adding a rule is always cheaper than understanding the existing ones, and nothing ever fails when a rule goes unused. The fixes are structural: colocate CSS with what it styles, keep the global layer a closed set with an owner, allow raw values only in the token file, and enforce it in CI rather than in review.

solid answer

~50 s

I treat unbounded growth as a symptom of two properties of CSS, not of sloppiness: adding a rule is always cheaper than understanding the ones already there, and nothing ever breaks when a rule stops being used. So conventions alone will not hold. What works is making the cheap path the correct one. Colocate each component's CSS with the component, so deleting the feature deletes the CSS without anyone proving it dead. Keep the entry file to nothing but ordered imports, so the architecture is readable and reviewable in one screen. Treat the global layer — reset, tokens, base — as a closed set with a named owner, so an addition there is a deliberate decision. Allow raw colour and spacing values only in the token file, enforced by a linter, not by reviewers' memory. And track CSS bytes as a CI signal so growth is visible before it needs archaeology.

go deeper

for a junior

Focus on the habit that matters most for you: put a component's styles in that component's file and consume shared tokens rather than typing raw colour values. Know that a new rule appended to the global sheet is very hard to remove later.

for a middle

Be able to explain why stylesheets only grow — appending is cheaper than understanding, and unused rules never fail — and name the structural answers: colocation, an entry file of imports only, and tokens as the single source of raw values.

for a senior

Show how you would introduce these into an existing codebase incrementally: scoping lint rules to changed code, baselining legacy violations, and choosing which convention to enforce first based on where the team actually loses time.

for a principal

Own the economics and the second-order effects: which conventions get machine-checked and which stay advisory, what a gate displaces rather than prevents, how a byte budget can be gamed, and when the honest answer is that the stylesheet is not the problem worth funding.

## Why stylesheets only ever grow Two properties of CSS combine badly. First, **adding is cheaper than understanding**. Given a page that looks wrong and a stylesheet you did not write, appending a new rule takes minutes and modifying an existing one requires knowing everything else it affects. Under deadline, everyone appends. This is rational behaviour at the individual level producing a bad outcome at the system level, which means exhortation will not fix it. Second, **nothing ever fails**. A rule that no longer matches anything produces no error, no warning, no test failure. The feedback loop that keeps dead code out of every other part of the codebase simply does not exist here. Any real intervention has to change one of those two facts. Conventions in a wiki change neither. ## Colocation: make deletion automatic The highest-leverage change is that a component's CSS lives with the component, in a file whose lifetime is the component's lifetime. Removing the feature removes its styles as a mechanical consequence — no audit, no proof, no courage required. This is the only mechanism that reliably makes a stylesheet *shrink*, because it removes the need for anyone to decide. *Cost*: the design becomes harder to see whole. Nobody can read one file and know what the app looks like, and without a strong token vocabulary the same shadow gets reinvented in four component files. Colocation only works when it is paired with shared tokens. ## The entry file as the contract One entry file containing nothing but ordered imports — reset, tokens, base, layout, components, utilities — turns the project's cascade order into a single reviewable artifact. A newcomer reads twenty lines and knows what may override what; a pull request that reorders two of them is visibly a change to override semantics rather than a formatting tweak. *Cost*: it is a chokepoint. Every feature branch touches it, so it collects merge conflicts, and the discipline of keeping actual rules out of it has to be enforced or the file quietly becomes another dumping ground. ## A closed global set with a named owner Reset, tokens and base should be a set that changes when the design system changes, not when a feature ships. Naming an owner for those files — a required reviewer — makes an addition a decision rather than a diff. The measurable goal is that the global layer's size stays roughly flat while component count grows. *Cost*: friction, and friction gets routed around. The predictable failure is people adding to a `utilities` file instead, because it is the unguarded door. Whatever you gate, watch what grows next to it. ## One source for raw values Colour and spacing literals belong in the token file; everywhere else consumes them through `var()`. This is what makes a theme change a one-file edit instead of a search-and-replace, and what stops eleven nearly-identical greys from accumulating. Crucially it is machine-checkable — a linter such as stylelint can forbid hex colours outside the token file (`color-no-hex`), which moves the rule out of reviewers' memory. *Cost*: token sprawl. If every design request mints a new token, you have moved the growth problem rather than solved it. Someone has to own saying no to `--color-blue-7`. ## Enforcement or it decays The general principle: **a convention that is not machine-checked is a convention that decays**, because review attention is finite and reviewers rotate. Anything you actually care about should fail in CI — lint rules for the value discipline, a check that the entry file contains no style rules, a required reviewer on the global folder. *Cost*: false positives on legacy code. Introducing a rule to a large existing sheet needs a baseline or a per-directory scope so the gate applies to new code without demanding a big-bang cleanup nobody has time for. ## Budgets as a signal, not a verdict A CI check on total CSS bytes turns growth into a visible number that moves. It is far more honest than periodic audits, because it reports the trend continuously. *Cost*: byte count is a proxy. It can be gamed, and it says nothing about whether the CSS is comprehensible. Use it as a conversation trigger — a sudden jump asks "what happened here?" — not as a pass/fail verdict that people learn to route around. ## What ties it together Every item above works by changing the economics rather than the intentions: deletion becomes free, the correct destination for a new rule becomes obvious, and the wrong choice fails in CI instead of surviving review. Announce a convention and it survives about a quarter. Encode it in the file structure and the pipeline, and it survives the people who wrote it.

  • You add a required reviewer on the global folder and the global folder stops growing — but the utilities file doubles. What happened?
    The friction did its job locally and pushed the behaviour somewhere unguarded. That is the normal response to a gate, not a failure of the team. The lesson is to instrument the whole surface rather than one folder: watch total CSS and each layer's share, so displacement shows up as a shifted distribution instead of a success story on one metric.
  • How would you introduce a lint rule forbidding raw hex colours to a stylesheet that already contains hundreds of them?
    Scope it so it fails only on new and changed code — a baseline file, or enabling it per-directory as areas are migrated. A gate that fails the whole build on day one gets disabled within a week. The goal is to stop the inflow first; the existing violations are then paid down opportunistically as those files are touched anyway.
  • Is a CSS byte budget a good CI gate, and where does it mislead?
    It is an excellent signal and a poor verdict. It reports the trend continuously, which is more honest than an annual audit, but bytes say nothing about comprehensibility — a well-factored sheet and an unreadable one can weigh the same, and heavy compression flatters duplication. Use a jump as a prompt to look, not as an automatic block.
  • When is a large global stylesheet not worth fixing?
    When the churn is elsewhere. If the app is stable and the CSS is not the source of defects or slow changes, the growth is cost you already paid, and a migration spends real weeks to buy tidiness. Intervene where the pain is measurable — repeated visual regressions, long onboarding, fear of touching a file — not because a line count offends.

saying these in an interview costs you the question

  • Believes a documented convention is enough without enforcement
  • Plans a big-bang stylesheet rewrite as the fix
  • Treats CSS byte count as a quality measure
  • Gates one folder and ignores where the growth moves
  • Assumes reviewers will remember the token rules

context