Skip to main content
design systems

Escaping MUI Without Stopping the Roadmap

A migration plan for teams stuck between a component library's sx prop and a design system that's actually theirs: tokenize first, strangle second, and never schedule a rewrite.


8 min read

Every team that built fast on a component library eventually asks the same question: how do we get a design system that's ours — tokens, governance, a real source of truth — without freezing feature work for two quarters?

The wrong answer is a rewrite. The right answer is boring, gradual, and starts nowhere near the component library.

I want to be precise about where I'm speaking from. I haven't run a big-bang MUI-to-Tailwind conversion, and I'd be suspicious of anyone selling you one. What I have done is the load-bearing move underneath any such migration: at a logistics SaaS, I replaced a Material Design data table — the heaviest, most customized surface in the product — with a custom, token-driven component, while the rest of the app stayed on Material and the roadmap kept moving. That's the shape of every sane escape from a component library. Here's the generalized version.


Why "Rewrite" Is the Trap

A rewrite feels clean because it defers all the hard decisions to a mythical future where you have time. You don't have that time, and the rewrite has a second problem: it's all-or-nothing. Until it's done, you're maintaining two systems and shipping features in neither. Halfway through, priorities shift — they always do — and you're left with a half-migrated codebase that's worse than either starting point.

The escape has to be incremental by construction: valuable at every step, abandonable at every step, and never blocking the roadmap. That rules out "rewrite" and rules in "strangle."

Step 1: Tokenize First — and MUI Becomes a Renderer

Here's the counterintuitive part: the first move has nothing to do with removing the component library. It's extracting your decisions out of it.

Every color, spacing value, radius, and type choice currently lives inside sx props and theme overrides — scattered, duplicated, and impossible to change globally. Pull them into a token layer: CSS custom properties, generated from a source of truth. (Style Dictionary is a good compiler for this; the point is that tokens are naming decisions before they're values.)

Then point the component library's theme at those tokens. MUI's createTheme, styled-components' ThemeProvider, whatever you're on — feed it your custom properties instead of hardcoded values.

The moment you do this, something quietly important happens: the library stops being your source of truth and becomes a renderer. Your decisions live in your tokens now. The library is just one consumer of them. You haven't removed a single component, and you've already won the most important battle — ownership of the design decisions themselves. If you stopped here, you'd still be better off.

Step 2: Define the Boundary, Then Strangle

Now you migrate components, but you never migrate all at once. You draw a boundary rule and let it do the work:

New components read tokens directly and use your own primitives. Existing components keep the library until they're touched for another reason.

This is the strangler pattern: the new system grows around the old one, and the old one shrinks as normal feature work brushes against it. A card that needs a change this sprint gets rebuilt on your primitives as part of that change. A card nobody touches stays on the library indefinitely, harmlessly, because it's reading your tokens anyway.

Start the strangle where I did — with the highest-customization surface, not the simplest. The component you've fought the library hardest to bend is the one where owning the implementation pays back immediately, in both craft and bundle size. At Tranzport that was the data table; the Material version couldn't express how dispatchers actually scan, and the custom component that replaced it was lighter and better. The low-customization stuff — a plain button, a text input — can wait, because the library serves it fine.

Step 3: Decide the Endgame Last

Notice what we still haven't done: chosen the destination. Tailwind? Vanilla CSS with your tokens? A thin in-house primitive layer? On purpose — because once your decisions live in tokens, the endgame is a swap, not a bet.

Tailwind v4's @theme maps essentially one-to-one onto a token layer — your custom properties become the theme, and utilities read them. Vanilla CSS reads the same custom properties with no build step at all. A headless-primitive approach (the kind I build in the ReactZero libraries) reads them too. All three are viable because the tokens already exist. You're no longer betting a quarter on a framework choice; you're picking a rendering strategy for a system that already works, and you can even mix them.

Deciding the endgame first is how teams end up in the rewrite trap. Deciding it last is how you keep every option open until you've earned the information to choose well.

The Dependency Audit Nobody Runs

One concrete worksheet, because it changes the estimate more than anything else: list which of the library's components you actually use, not which you're afraid you use.

Most teams import a 40-component library and use nine. The migration isn't "replace MUI"; it's "replace the nine surfaces that matter and delete the dependency." That reframing routinely turns a quarter-long dread into a few sprints of ordinary work, slotted between features, invisible on the roadmap.


The pattern under all of it: own your decisions before you change your renderer. Tokenize first, and MUI becomes a detail you can migrate at your leisure instead of a foundation you have to replace in a panic. The rewrite was never necessary. The strangle was always enough.

More on the token architecture this depends on: the design system behind this site, five themes from one source, and why theme and design system aren't the same thing.