Apply

Migrating existing CSS

Good migration is not heroic rewrites. It is controlled improvement. You should not need to pause delivery, rebuild the whole frontend, or pretend the old CSS is not still in the room.

Ask this first

Ask first: Can this be safely replaced now?

  • Yes — replace properly.
  • No — contain it and improve around it.

Most migration mistakes come from trying to fix everything at once. Decide whether the code should be replaced or safely contained.

Migration principles

Contain first

Put existing CSS somewhere predictable before changing how new CSS is written.

Improve active code

Fix the CSS people touch every week, not the fossil no one has opened since 2018.

Delete over time

Migration succeeds when legacy CSS gets smaller, not when it gets renamed and promoted.

Recommended migration path

Work through the steps in order. Skip ahead only when a later step does not depend on the foundation you have not laid yet.

  1. Introduce the layer order first.

  2. Move old CSS into the legacy layer.

  3. Put tokens and settings in the settings layer.

  4. Move broad element defaults into base.

  5. Write all new work as proper component partials.

  6. Reduce selector depth when touching existing code.

  7. Replace unclear names with semantic component ownership.

  8. Use modifiers for variants and state classes for temporary state.

  9. Move emergency fixes into hacks with comments.

  10. Delete old CSS when the UI is replaced.

Common migration milestones

Use these checkpoints in planning and retrospectives. They describe outcomes, not calendar dates.

  • Milestone 1

    Contain legacy

    Layer order is defined and inherited CSS is isolated in legacy or thirdparty.

  • Milestone 2

    Stabilise new work

    New components follow naming, state, and token rules without exceptions.

  • Milestone 3

    Shrink legacy

    Legacy selectors and files get smaller release by release.

  • Milestone 4

    Retire hacks

    Temporary fixes are tracked and removed on a regular cadence.

Expected effort range

Timelines vary with legacy surface area and how often teams ship. Treat ranges as planning guides, not deadlines.

  • Small codebase

    1–3 sprints

    Enough for a stable migration baseline when one team owns most of the CSS.

  • Mid-size codebase

    1–2 quarters

    Broad consistency across active products and shared partials.

  • Large or multi-team

    Multiple quarters

    Phased rollout with governance, reviews, and measurable legacy reduction.

Step 1: define the cascade order

Add layer order before moving code. This gives the project a stable override model immediately and stops specificity wars from breeding.

CSS code example
@layer legacy, settings, base, utilities,
layout, components, theme, hacks;

Step 2: isolate legacy CSS

Inherited first-party CSS belongs in the legacy layer so the rest of the project can move forward without pretending old code is modern architecture. If vendor CSS is separate, use an optionalthirdparty layer declared beforelegacy in the @layer list — seelegacy and third-party layers.

CSS code example
@import './legacy/site.css' layer(legacy);
@import './components/product-card.css' layer(components);

Importing old CSS into the correct low layer means every intentional layer above it has clearer ownership and safer override behaviour.

What not to do

  • Do not rename every class and call it migration.
  • Do not move legacy CSS into components and call it clean.
  • Do not let hacks become permanent architecture.
  • Do not add specificity when layer order solves the problem.
  • Do not block delivery for a rewrite plan with no clear business case.

Migration should reduce risk, not create a dramatic new source of it.

Definition of done

Migration is working when new CSS follows the system, old CSS is contained, overrides are predictable, and the legacy layer keeps shrinking over time.

Next useful pages