Web Development 5 min read 1010 words

More CSS, less JavaScript: how GitHub left CSS-in-JS behind

ES
More CSS, less JavaScript: how GitHub left CSS-in-JS behind

After several days writing about agents escaping their sandboxes, I felt like coming back to something more down to earth. And the GitHub team has just published exactly the kind of article I like reading on a Sunday: how they improved site performance by shipping more CSS.

The title sounds like a contradiction, but it isn’t. What they did was take styles out of JavaScript and put them back where they always belonged: in CSS files. A migration of almost three years that ended in June 2026 with GitHub running on 100% CSS Modules.

The problem: CSS-in-JS at scale

GitHub builds its interface with Primer, its design system. For years, Primer React components used styled-components, one of the most popular CSS-in-JS libraries: styles are written in JavaScript, next to the component, and generated at runtime.

It’s a very convenient idea. Everything is in one place, you get TypeScript types, you can use design tokens directly… But it has a cost, and that cost grows with the number of components. Around 2023, the number of components on some GitHub pages exploded and three problems appeared:

  • Initial page loads took longer because styles were initialised on the client.
  • Server-side rendering (SSR) got worse as it had to collect styles.
  • Style updates became unmanageable as pages grew.

Put another way: every component on screen was a bit of extra JavaScript the browser had to run just to know what colour to paint a button.

The solution: CSS Modules

The alternative they chose was CSS Modules. Styles are written in a .css file next to the component, class names are local by default (so they don’t collide) and, most importantly, there’s nothing to run on the client or the server. Everything compiles down to normal stylesheets that ship with the HTML.

The difference, simplified, is this:

// Before: CSS-in-JS with the sx prop, resolved at runtime
<Box sx={{ display: 'flex', gap: 2, color: 'fg.muted' }}>
  ...
</Box>
/* After: Toolbar.module.css, compiled to static CSS */
.toolbar {
  display: flex;
  gap: var(--base-size-8);
  color: var(--fgColor-muted);
}
import styles from './Toolbar.module.css'

<div className={styles.toolbar}>...</div>

You lose some of the magic of having everything in a JavaScript object, but you gain something far more valuable: the browser does what it does best, which is applying CSS.

How to migrate a site like GitHub without breaking it

This is the part of the article I find most interesting, because it applies to any large project. The recipe was always the same, component by component:

flowchart LR
    A["New CSS Module file · next to the old style"] --> B["Feature flag · toggles between both"]
    B --> C["Visual regression tests · identical snapshots"]
    C --> D["Gradual rollout · team, staff, everyone"]

No big-bang change. Both systems living side by side, a switch to go from one to the other, and visual tests to check the result was pixel-identical. If something broke, the flag was turned off.

Primer finished migrating all its components in December 2024, and the numbers made the effort worthwhile:

MetricImprovement
Server-side render time55% less
Component initialisation time25% less

The hard part: thousands of sx props

Migrating the design system was only half of it. The rest of GitHub was still using the sx prop, which let any team customise a component with an inline style object. It was the best and worst of CSS-in-JS at once: extremely convenient to use, and expensive to run.

So as not to block anyone, they created a bridge package, @primer/styled-react, which wrapped the new components and still accepted sx. That way, code that didn’t use it already benefited from the improvements, and code that did could be migrated little by little.

And here’s the number that struck me most:

Phasesx props migratedTeamTime
April 2025 – October 20256,419 (from a peak of ~7,760)A rotation of 8 engineers, with a VS Code plugin and codemods6 months
April 2026The last 8952 engineers and Copilot agents3 weeks

The work didn’t change. The tools did. A year later, two people with coding agents did in three weeks what had previously taken a larger team half a year. And they did it with the same safety net as always: feature flags, visual tests and gradual rollout. The agents sped up the work, but they didn’t replace the process that kept things from breaking.

After that, they still had to decouple the themes (GitHub has seven, each with its high-contrast variant) from styled-components. Another two months. In June 2026 they finally removed sx, styled-components and styled-system from github.com.

As a curiosity, while they were preparing all this, styled-components announced it was going into maintenance mode. There’s no better confirmation that they were heading in the right direction.

What I take away

The first lesson is that the best JavaScript is the JavaScript that never runs. For a few years we convinced ourselves that putting styles in JavaScript was the modern way to do things, and for many projects it was. But CSS has come a long way —native variables, @layer, nesting, :has()— and today almost everything that led us to CSS-in-JS can be done with plain CSS compiled at build time. This very blog works that way with Tailwind v4: all the CSS is generated when the site is built, and the browser runs nothing to apply it.

The second is about the how. What makes this article valuable isn’t the choice of CSS Modules, but the discipline of the migration. Both systems coexisting, a switch for every change, tests that compare snapshots and phased rollouts. It’s boring, it’s slow, and it’s exactly what lets you change the architecture of a site used by millions of people without anyone noticing.

And the third is that long, tedious migrations are perfect territory for coding agents, as long as the safety process is already in place. Going from 895 to 0 in three weeks wasn’t magic: it was repetitive, well-scoped work with a clear way to check whether each change was right. Under those conditions, an agent is a huge help.