Primer Design System 支撑着如今你在 GitHub 上看到的许多体验。从按钮到横幅再到面包屑导航,这些基础组件必须在各种场景下都具备可访问性、灵活性和高性能。
早在 2023 年,某些页面上的组件数量开始激增。这给我们现有的 CSS-in-JS 方案带来了若干与性能相关的挑战:
- 由于样式在客户端初始化,初始页面加载耗时更长
- 随着样式收集从客户端转移,服务端渲染性能下降
- 随着页面上组件数量的增长,样式更新逐渐失控
很明显,Primer 团队需要从根源上解决这个问题。我们需要找到一种替代方案,彻底避免当前方案所面临的客户端和服务端开销。最重要的是,我们选择的任何替代方案都必须以在迁移期间不会对 GitHub 造成任何破坏的方式运作。
引入 CSS(Modules)
Primer 团队找到了一个满足我们所有标准的解决方案:CSS Modules。这种格式让我们能够做我们最喜欢的事情之一:编写并使用原生 CSS 特性,同时仍然保留我们从 CSS-in-JS 中所期望的一定程度的同置与封装。
使用 CSS Modules,样式会与组件的 JavaScript 源码一起写在同一个 CSS 文件中。它还允许我们默认将所有类名视为局部作用域,从而避免全局选择器可能带来的一些冲突和难题。这种形式也消除了对任何客户端或服务端运行时行为的需求。相反,样式会被汇总进 CSS 样式表,作为页面 HTML 的一部分一并发送。
然而,这一方案与我们当时采用的 CSS-in-JS 方案截然不同。这一变更需要更新每一个 Primer 组件,以及 GitHub 中每一个使用该技术编写的组件。所幸,设计系统正是大规模推行这类变更的完美载体。
逐步迈向 CSS Modules
转向 CSS Modules 的形势已经明朗。Primer 团队需要为其每个组件交付更新,将它们从 CSS-in-JS 迁移到 CSS Modules。与此同时,我们对这些组件所做的更新不能破坏 GitHub 中的任何使用。最后,我们用于 CSS-in-JS 的底层技术也必须继续对 GitHub 中当前正在使用它的任何组件保持可用。
在这些约束条件下,我们决定采用增量迁移策略,使我们能够安全地发布组件更新而不至于引发混乱。对于每个组件,我们的计划是:
- 新增一个文件,将现有样式转换为 CSS Modules
- 将该组件添加到一个功能开关中,用于在新旧样式之间切换
- 利用现有的视觉回归测试来验证我们的 CSS-in-JS 方案与 CSS Modules 之间的快照是否完全一致
- 逐步向我们的团队、然后是 GitHub 员工、最后向所有 GitHub 用户推出该功能开关,以便在此过程中及时发现任何问题
这一流程形成了一个强大的反馈闭环,随着 Primer 持续将这些变更交付到 GitHub,问题在流程早期就被标记出来。功能开关的使用让我们能够安全地完成这次迁移,同时为我们提供了关于 CSS Modules 性能收益的清晰信号。
到 2024 年 12 月,Primer 中的所有组件都已通过这一流程迁移到 CSS Modules。我们在各个方面都看到了性能提升,尤其是:
- 页面服务端渲染时间减少了 55%
- 页面上组件的初始化时间减少了 25%
在 Primer 中开展这项工作带来了明显的性能提升,于是我们开始思考,在 GitHub 的其他部分进行这些转换是否也能获得类似的性能收益。同样地,我们还需要多久才能最终在全公司范围内放弃对 CSS-in-JS 的支持?
在 GitHub 告别 CSS-in-JS
从 Primer 中移除我们的 CSS-in-JS 方案,最棘手的部分之一在于 sx prop 的使用。这个 prop 曾是为 Primer 组件设置样式和进行自定义的方式。团队可以提供一个内联对象来自定义组件的方方面面。它代表了 CSS-in-JS 最好的一面,也代表了最糟的一面:
- 出色的 TypeScript 支持,并与我们的 Design Tokens 集成
- 与组件同处一地,因此一切都集中在一个地方
- 由于用于
sx的内联对象具有动态特性,运行时开销很高 - 随着页面上使用
sx的组件数量增长,扩展变得困难
因此,我们摆脱 CSS-in-JS 之旅的第一步,就是减少 sx 在 GitHub 各处的使用。这让我们能够立即提升性能,效果类似于我们迁移 Primer 组件时所获得的收益。它也为我们从产品中彻底移除 CSS-in-JS 做好了完美铺垫。
Primer 的双重性
需要注意的是,虽然设计系统本身已正式停用,styled-components但 GitHub 代码库本身的大部分内容却并非如此。由于sxprops 多年来一直是 GitHub 事实上的样式标准,我们面临着数千个sxprops 需要迁移,之后才能考虑将 GitHub 迁移到这套简洁全新的@primer/react版本上,而该版本不再依赖styled-components.
那么……我们是如何在完成如此庞大工作量的同时,还提升了信心并降低了风险?答案是:并非一蹴而就。
最初的 CSS 迁移比我们对外透露的要更微妙一些:除了将组件迁移到 CSS modules、通过功能开关在生产环境中测试并逐步灰度发布之外,我们还在一个名为 @primer/styled-react 的传递性库中创建了“包装”组件。这个包的整个目的,是让 sx 的用法能够进入新迁移的组件中。这样一来,GitHub UI 代码库中使用该 prop 的实例,可以通过 @primer/styled-react 导入同一个组件来继续使用它们,与此同时,对于那些不需要该 prop 的场景,我们则通过直接从 @primer/react 导入获得了性能收益。
Styled Box Zero
迁移过程的下一阶段如下:
- On a package-by-package basis:
- 将所有
sx用法翻译为等效的 CSS modules 文件。这包括交叉引用(参见 迁移到 CSS 变量) - 将
@primer/styled-react导入替换为@primer/react导入 - 在预生产环境中测试
- 部署
- 将所有
说来也巧,就在我们准备着手这项浩大工程之际,styled-components 维护模式 被宣布了,这进一步印证了我们正朝着正确的方向迈进。
这项工作于 2025 年 4 月启动,待迁移的 sxprops 峰值约为 7,760 个;直到 2026 年 5 月我们才看到它最终落地。起初,我们内部一位出色的开发者 Ian Sanders 创建了一个 VS Code 插件,用于辅助逐个 prop 的迁移。我们还在内部开发了一个类似的 codemod,并用它来迁移 GitHub 代码库中的整个文件。这项工作虽然需要一些人工监督和仔细验证,但大部分是自动化的。8 名工程师轮班工作,在 6 个月的时间里迁移了 6,419 个 props,观察到服务端渲染时间性能提升幅度从 1% 到某些页面的 22% 不等。

在 GitHub 的另一侧,Copilot 的能力正呈指数级增长。AI 变得越来越聪明、越来越强大;在这项工作仍在进行期间,我们发布了 Copilot coding agent 和 Copilot code review。
等到我们重新拾起这项工作时,已经是 2026 年 4 月,局面已大不相同;我们得以在三周内将 sxprops 从 895 个降到 0 个,靠的是一名工程师组成的团队、不屈不挠的决心,以及大量 Copilot 编程智能体。

主题之争
这是个大日子:我们终于完成了sx那些横亘在我们与彻底styled-components移除之间的迁移工作,历经数年之久……我们终于可以清理掉这些依赖,转向其他更令人兴奋的工作了,对吧?错!
GitHub 支持七种不同的主题,它们全都提供高对比度模式变体。而这一切的实现,靠的正是你猜到的那个东西——styled-components。在我们考虑移除这些依赖之前,首先需要把主题系统解耦。
现在,这件事并没有听起来那么严重。我们的主题变量一直是通过 @primer/css 包在 CSS 中定义的,而且我们在 2025 年底迁移 @primer/react 时就已经为非样式化的主题化做了前瞻性规划。我们需要移除的是由 styled-components 启用的 JavaScript 用法和工具函数。于是,我们又一次投入了工作。
到这一步你应该已经熟悉套路了:执行迁移、逐步推出、为一切加上功能开关。两个月后,期间经历了一些小波折,我们终于可以全面移除依赖了;我们甚至为此也加了功能开关。小心驶得万年船。
结局好,一切都好
截至 2026 年 6 月,GitHub 已 100% 运行在 CSS modules 之上。我们设置的各项防护措施使我们能够安全地推出重大架构变更、在生产环境中进行压力测试、高效地捕获错误、调整方向并修复问题,最终让我们成功达成目标,并在此过程中实现了显著的性能提升。
起初看似一次 CSS 迁移的工作,最终演变为对 GitHub 如何大规模地设计样式、应用主题和交付 UI 的一次渐进式重新平台化。到最终,我们不仅从 dotcom 中移除了 sx、styled-components 和 styled-system,而且在整个过程中没有让 GitHub 出现故障。提升产品的性能、用户体验和愉悦感,始终是 GitHub 全体成员最关注的事情。
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
sxon 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-reactimports with@primer/reactimports - Test in pre-production
- Deploy
- Translate all
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.

In a different side of GitHub, Copilot’s capabilities were increasing exponentially. AI was getting smarter, more capable; we released Copilot coding agent and Copilot code review while this work was still underway.
By the time we picked this work back up, now April of 2026, the panorama was different; we were able to get down from 895 to 0 sxprops in the span of three weeks with a team of two engineers, relentless determination, and a whole lot of Copilot coding agents.

Battle of the themes
It was a big day: we had finally completed the sx migrations that were standing between us and full styled-components removal, years in the making… we can finally clean up these dependencies and move on to different, more exciting work, right? Wrong!
GitHub supports seven different themes, all of which offer a high contrast mode variation. All of it enabled through, you guessed it, styled-components. Before we can even think of removing these dependencies, we need to decouple our theming.
Now, this isn’t as huge a deal as it sounds. Our theming variables have always been defined in CSS through our @primer/css package, and we already planned forward for non-styled Theming when we migrated @primer/react in late 2025. It’s the JavaScript usage and utilities that are enabled by styled-components that we needed to remove. Once again, we got to work.
You know the drill by now: perform the migrations, roll it out slowly, feature flag everything. Two months and a few hiccups along the way later, we were all-systems go for dependency removal; we even feature flagged that. Better safe than sorry.
All’s well that ends well
GitHub has been running on 100% CSS modules as of June 2026. The safeguards we put in place enabled us to roll out significant architectural changes safely, stress test in production, catch errors, pivot and repair efficiently, ultimately allowing us to succeed in our goals and realize great performance gains along the way.
What looked at first like a CSS migration turned out to be a gradual re-platforming of how GitHub styles, themes, and ships UI at scale. By the end, we had not only removed sx, styled-components, and styled-system from dotcom, but completed it without breaking GitHub along the way. Enhancing the performance, user experience and delight of our products continues to be top of mind for all of us here at GitHub.