Updates10:09 GMST
Oct 4, 2026

Improving site performance by shipping more CSS

The Primer Design System powers many of the experiences you see on GitHub today. From buttons to banners to breadcrumbs, these foundational components are required to be accessible, flexible, and performant across a wide variety of scenarios.


Back in 2023, the number of components on certain pages began to explode. This led to several performance-related challenges with our existing CSS-in-JS solution:


Initial page loads took longer due to styles being initialized on the client

Server-side rendering performance declined as style collection shifted from the client

Updates to styles grew out of control as component count grew on a page

It became clear that the Primer team needed to address the issue at the source. We needed to find an alternative that would completely avoid the client and server costs that we were seeing with our current solution. Most importantly, any alternative we pick would need to work in a way that would avoid any breakage to GitHub during the migration.


Introducing CSS (Modules)


The Primer team found a solution that met all of our criteria: CSS Modules. This format would allow us to do one of our favorite things: write and use native CSS features, while still allowing some amount of the colocation and encapsulation that we had come to expect from CSS-in-JS.


With CSS Modules, styles would be authored in a CSS file alongside the JavaScript source for the component. It would also allow us to treat all class names as local by default, preventing some of the collisions and challenges that can come from global selectors. This format also removes the need for any client or server runtime behavior. Instead, styles would roll up into CSS stylesheets that were sent as part of the HTML for a page.


However, this solution was radically different from the CSS-in-JS solution we had at the time. This change would require an update to every Primer component and every component at GitHub authored using this technique. Thankfully, design systems are a perfect vehicle to deliver this kind of change at scale.


A gradual march towards CSS Modules


The situation for moving towards CSS Modules was clear. The Primer team would need to deliver updates to each of its components, moving them from CSS-in-JS to CSS Modules. At the same time, updates we made to these components could not break any usage in GitHub. Finally, the underlying technique we used for CSS-in-JS also had to continue working for any components in GitHub that were currently using it.


With all these constraints in place, we decided on an incremental migration strategy that would allow us to safely ship component updates without breaking the world. For each component, our plan was to:


Add a new file that translated existing styles to CSS Modules

Add the component to a feature flag that would toggle between the new and old styles

Use existing visual regression tests to verify snapshots were identical between our CSS-in-JS solution and CSS Modules

Gradually roll out the feature flag to our team, then to GitHub staff, and finally to all GitHub users to catch any issues along the way

This process created a strong feedback loop where issues were flagged early in the process as Primer continuously delivered these changes to GitHub. The use of feature flags allowed us to do this migration safely while giving us clear signals on the performance benefits of CSS Modules.


By December 2024, all components in Primer were migrated over to CSS Modules using this process. We saw performance wins across the board, in particular:


55% less time to server-side render a page

25% less time for components on a page to initialize

With clear performance wins from doing this work in Primer, we began to wonder if we could see similar performance wins by doing these conversions in other parts of GitHub. Similarly, how long until we could ultimately drop support for CSS-in-JS across the company?


Moving away from CSS-in-JS at GitHub


One of the trickiest parts about removing our CSS-in-JS solution from Primer was due to the usage of the sx prop. This prop was the way to style and customize components from Primer. Teams could provide an inline object to customize everything about the component. It represented the best and worst parts of CSS-in-JS:


Excellent TypeScript support with integration with our Design Tokens

Co-located with the component so that everything was in one place

High runtime cost due to the dynamic nature of inline objects used for sx

Difficulties scaling as the number of components using sx on a page grew

As a result, the first part of our journey to move away from CSS-in-JS was to reduce sx usage across GitHub. This would allow us to immediately improve performance similar to the wins we saw when migrating Primer components. It also set us up perfectly for removing CSS-in-JS entirely from the product.


The Duality of Primer


It’s important to note that, while the design system itself was officially off styled-components, a large part of the GitHub codebase itself wasn’t. With sx props having been the de facto styling standard at GitHub for years, we were looking at thousands of sx props that needed migration before we could even think of getting GitHub onto the sleek new @primer/react version which didn’t rely on styled-components.


So… how did we do this immense amount of work while increasing confidence and reducing risk? The answer: not all at once.


The original CSS migration was a little bit more nuanced than we led on: in addition to migrating the components to CSS modules, feature flagging to test in production and slowly rolling them out, we also created “wrapper” components in a transitive library we called @primer/styled-react. The whole purpose of this package was to allow sx usage into the newly migrated components. This way, the instances of the GitHub UI codebase that were using this prop could continue to consume them by importing the same component through @primer/styled-react, while we realized the performance gains of importing straight from @primer/react for the cases that didn’t.


Styled Box Zero


The next phase of the migration process was as follows:


On a package-by-package basis:

Translate all sxusage into equivalent CSS modules files. This included cross referencing (See migrating to CSS variables)

Replace @primer/styled-react imports with @primer/react imports

Test in pre-production

Deploy

Curiously enough, while we were getting ready to undertake this massive effort, styled-components maintenance mode was announced, offering further confirmation that we were taking steps in the right direction.


The work kicked off April 2025 with a peak of ~7,760 sxprops to be migrated; we wouldn’t see it realized until May 2026. Initially, one of our great in-house developers, Ian Sanders, created a VS Code plugin that would assist with per-prop migration. A similar codemod was developed internally and utilized to migrate entire files in the GitHub codebase. The work, while requiring a bit of manual oversight and careful validation, was mostly automated. A rotation of 8 engineers migrated 6,419 props over the course of 6 months, observing Server-Side Rendering time performance gains ranging from 1% up to 22% in some pages.

Improving site performance by shipping more CSS
1 views