Eight months ago, our team faced a growing performance debt. Our production frontend bundle had bloated over 450KB of utility CSS rules, slow purge build steps, and specificity conflicts across team feature branches.

We made a bold decision: refactor the entire 50,000 line-of-code application back to modern Vanilla CSS. In this retrospective, I share the exact migration playbook, build performance metrics, and hard architectural lessons we discovered along the way.

Table of Contents

1. Why We Decided to Migrate

Utility frameworks start fast on day one, but as enterprise products grow to hundreds of components, the lack of centralized semantic design tokens creates maintenance friction. By leveraging native CSS Cascade Layers (`@layer`) and CSS Custom Properties, we eliminated 82% of CSS payload while enforcing strict design system consistency.

2. Structuring Modern CSS Tokens & Layers

We organized our CSS directory using standard `@layer` rules to guarantee deterministic specificity order without needing `!important` hacks:

/* Global Cascade Layer Setup */
@layer reset, tokens, components, utilities;

@layer tokens {
  :root {
    --color-primary: #38bdf8;
    --color-surface: #0f172a;
    --radius-md: 8px;
    --space-gap: clamp(1rem, 2vw, 2rem);
  }
}

@layer components {
  .btn-primary {
    background: var(--color-primary);
    border-radius: var(--radius-md);
    padding: 0.75rem 1.5rem;
    transition: transform 0.2s ease;
  }
}

3. Refactoring Code & Performance Metrics

Post-migration benchmarks yielded immediate production gains: total main-thread CSS parsing time dropped from 380ms to 42ms on mobile devices, and Vite HMR build speeds improved by 4.2x during daily local development.

4. Mistakes & Hard Lessons Learned

The hardest part of the migration was removing deeply nested legacy overrides. Rule of thumb: always audit browser support for new CSS features (like `@scope`) before replacing polyfills, and write visual regression tests to catch layout shifts early.

Frequently Asked Questions

Did migrating to Vanilla CSS increase HTML verbosity?

On the contrary! By using semantic component class names instead of long strings of 15+ utility classes per element, our raw HTML output decreased by 18% in total file size.

How do you handle dark mode scoping in Vanilla CSS?

We handle dark mode by redefining CSS custom property tokens inside a `[data-theme='dark']` attribute selector on the root HTML element, requiring zero JS DOM manipulation for sub-elements.