Skip to main content

On 25 September 2026, GitHub published a post with a deliberately awkward title: Improving site performance by shipping more CSS. It is the story of removing one styling runtime from one codebase. It started in 2023 and finished in June 2026.

Most of the commentary has fixed on the last three weeks, where two engineers and a fleet of coding agents cleared the final 895 props. That is the quotable bit. The useful lesson sits in the gap between that sprint and the three years in front of it.

TL;DR

  • GitHub spent roughly three years removing styled-components from github.com, reaching 100% CSS Modules in June 2026. The wins were measured: 55% less server-side render time across the Primer design system.
  • Phase one moved 6,419 sx props with eight engineers over six months, about 31 per engineer-week. Phase two moved 895 with two engineers in three weeks, about 149. Agents were roughly five times faster at the mechanical work.
  • The calendar was set elsewhere: a compatibility package, feature flags, regression gates, and a theming coupling that surfaced only after the visible migration finished.
  • GitHub’s sharpest move was making the legacy API a separate import path, which turns a code review convention into a greppable, lintable fact.
  • The counts do not reconcile: 7,760 at peak, 6,419 migrated, 895 still left when work resumed. New sx props kept landing all the way through, because nothing blocked them.

Three years, one dependency

In 2023, component counts on some GitHub pages began to climb and the existing CSS-in-JS approach started to hurt on first load, on the server, and on every style update. By December 2024, every Primer component had moved to CSS Modules, for 55% less time to server-side render a page and 25% less time for components to initialise.

That was the design system. The product was a different problem. GitHub’s teams had spent years styling components with the sx prop, an inline object passed to any Primer component, and at peak there were roughly 7,760 of them. Eight engineers, on rotation, cleared 6,419 over six months from April 2025, with per-page render improvements of about 3% to just under 22%.

Then the work stalled while the company’s attention went elsewhere. When it resumed in April 2026, two engineers took the remaining 895 to zero in three weeks, leaning on Copilot coding agents.

The number everyone will quote, and the number that matters

Do the division: phase one runs at roughly 31 props per engineer-week (6,419 across about 208 engineer-weeks), phase two at roughly 149 (895 across six). Call it a 4.8x improvement in throughput per engineer on the same task.

Two caveats before anyone puts that on a slide: phase one included building the tooling, so setup drags its rate down, and the tail of a migration is never a fair sample. The direction is not in doubt. Agents are genuinely good at this category of work.

Now look at the calendar. The mechanical work, at its slowest, took six months out of a roughly 36 month programme. Even if agents had existed in 2023 and done all of it at the phase two rate, GitHub would have saved something in the order of five months on a three year project. The schedule was never bound by how fast anyone could rewrite a prop, which is where we would push back on the prevailing story about AI and legacy code. Agents compress the part of a migration that was already tractable. Without the scaffolding to change safely, an agent simply produces unreviewable diffs faster.

What the agents did not do

Two things consumed the actual calendar, and no coding agent shipping today does either for you.

The compatibility layer. GitHub could not migrate Primer and the product at the same time, so it built @primer/styled-react, a package whose own npm description calls it “a temporary package that bridges the gap between Primer React and styled-components”. Components could move to CSS Modules while consumers that still needed sx imported the wrapped version. That is what made the migration incremental rather than a big-bang rewrite, and it is a design problem, not a typing one.

The safety gates. Every component moved behind a feature flag, with existing visual regression tests verifying the CSS Modules snapshot was identical to the CSS-in-JS one, then rolled out to the team, to GitHub staff, and finally to everyone. That is the entire reason a three year re-platforming produced no visible outage. Flags with real targeting, a regression suite people trust, and pipelines that revert per component are prerequisites, and most teams asking us about migrations do not have them yet.

The import path is the lint rule

The most transferable idea here looks like packaging trivia. When GitHub wanted teams to stop using sx, they did not write a style guide entry. They moved the old behaviour to a different import path:

// Legacy: the styling runtime comes along for the ride
import {Button} from '@primer/styled-react'
<Button sx={{mt: 3, color: 'danger.fg'}}>Delete</Button>

// Migrated: no runtime, styles resolve in the stylesheet
import {Button} from '@primer/react'
import styles from './Delete.module.css'
<Button className={styles.delete}>Delete</Button>

That converts “please stop doing this” from something a reviewer must notice into something a machine can count. Remaining work becomes a grep, progress becomes a chart, and new usage becomes a CI failure:

// eslint.config.js
'no-restricted-imports': ['error', {
  paths: [{
    name: '@primer/styled-react',
    message: 'Legacy styling runtime. Use @primer/react with CSS Modules.',
  }],
}]

Apply it to whatever you are retiring: the old ORM, the old HTTP client, the old date library. Give it a distinct import path, then point your lint rules and dashboards at that path. A migration you can measure is a migration you can defend in a budget meeting, which is usually the difference between finishing and stalling at 80%.

The coupling you find last

After the props hit zero, GitHub still could not remove the dependency. Seven themes, each with a high contrast variation, were wired through styled-components utilities, and unpicking them took another two months.

Anyone who has run a migration to completion will recognise this. The visible work has a burndown chart; the invisible coupling has nothing, right up until it blocks the final step. Theming, test helpers, build config, an internal tool nobody owns: the last 5% of a dependency removal routinely costs as much as the middle 50%, and it is the part teams never put in the estimate.

Someone else’s calendar is running on your technical debt

A postscript worth sitting with. On 17 March 2025, a month before the sx migration began, Evan Jacobs, core maintainer of styled-components since around 2018, put the project into maintenance mode. He was fair about it: the API would not change and bugfixes would continue. He also wrote that he would not recommend adopting styled-components, or most other CSS-in-JS solutions, for new projects.

Note what that is not. It is not abandonware: release 6.5.3 shipped on 15 August 2026 and 7.0.0 prereleases were appearing as recently as 25 September 2026. It is something subtler and far more common, a healthy library whose own maintainer has publicly stopped recommending it, and no dependency dashboard can see it. Nothing is deprecated, no CVE is filed, the version number keeps moving.

The receipts sit in the package metadata. @primer/react carried a styled-components peer dependency at 5.x through the whole of version 37. Version 38.0.0, published on 27 October 2025, dropped it, and the current 38.40.0 has no trace of styled-components or styled-system. The bridge package still pins its peer dependency to 5.x, a major version behind the maintained line. Every temporary shim is a bet that you finish before someone else’s roadmap forces your hand. GitHub won that bet, with three years and a design system team.

What to take from this

  • Budget for the scaffolding, not the edits. The compatibility layer, the flags, and the regression gates are the project. The edits are a rounding error, and increasingly an automatable one.
  • Ship the gate first. New props kept landing all through GitHub’s migration, which is why its own totals do not reconcile. A lint rule blocking new usage on day one beats a codemod in month six.
  • Make the legacy path a different path. Separate import, separate package, separate namespace. Progress becomes something you can count.
  • Audit the non-obvious coupling before you estimate. Theming, tooling, tests, build config. That is where the overrun lives, and where agents will not help you.

REPTILEHAUS runs this work for clients who have neither three years nor a design system team: framework and dependency migrations, the feature flag and regression infrastructure that makes them safe, and the agent-assisted tooling that makes the mechanical middle cheap. The technique scales down far better than the headcount does, provided the scaffolding goes in first.

Carrying a dependency you have already decided to leave? Get in touch and we will scope what the exit really costs.


📷 Photo by Jon Tyson on Unsplash