Linear's Migration from styled-components to StyleX — Case Study
Case Study

From styled-components to StyleX: Linear's Migration

How Linear achieved ~30% faster page navigation by replacing runtime CSS-in-JS with compile-time StyleX — and what every React developer can learn from their approach.

July 1, 2026 8 min read By Oleg Maximov

"I've spent more tokens on this migration than I'd like to admit." That's Kenneth Skovhus, Linear engineer, opening his story about moving Linear's React applications from styled-components to StyleX. Over the past few months, Linear has been systematically migrating their codebase — and the results are compelling: roughly 30% faster render times when navigating between pages.

This isn't just another library swap. It represents a fundamental shift in how modern React applications handle styling — from runtime CSS-in-JS (where styles are generated and injected as the browser renders) to compile-time CSS-in-JS (where styles are pre-compiled during the build step). For anyone building React applications at scale, Linear's journey offers concrete lessons about performance, architecture, and the direction of the React ecosystem.

Why Linear Made the Switch

Linear's migration was driven by two converging forces: performance and maintenance.

The Performance Problem

Runtime CSS-in-JS libraries like styled-components generate CSS rules and inject them into the DOM while the application renders. This means users pay for style generation and rule injection on every page navigation. In a large application like Linear — a project management tool with complex UI surfaces — this overhead accumulates across dozens of components per page.

The performance cost is particularly noticeable during page transitions. When a user navigates between views in Linear, new components mount, styled-components generates their CSS, injects it into the document, and only then can the browser paint the new content. With StyleX, all of this work happens at build time — the generated CSS is a static asset the browser loads alongside the JavaScript bundle.

The Maintenance Tipping Point

The decisive factor was styled-components entering maintenance mode. After upgrading to React 18, Linear felt the impact directly: React introduced useInsertionEffect to help CSS-in-JS libraries inject styles more efficiently during concurrent rendering, but styled-components never adopted it.

Kenneth Skovhus traced the stalled PR to adopt useInsertionEffect and connected with its author, Cody Olsen at Sanity. They tested Sanity's optimized fork — which Sanity's write-up frames as a "last resort." The fork is a lifeboat, not a long-term plan.

What Linear Needed From Its Styling System

The migration was guided by five non-negotiables:

Why StyleX Won Over the Alternatives

Linear evaluated most React-compatible styling libraries. The closest alternative was vanilla-extract, which offers solid static extraction and type safety. However, its API felt fragmented, and requiring separate styling files didn't match how Linear prefers to work.

StyleX — Meta's compile-time CSS-in-JS solution — checked every box. It keeps styles local to components, provides a small API surface, deterministic style resolution through atomic CSS classes, type-safe interfaces, and strict guardrails that make it harder to restyle components from the outside. It's actively maintained by Meta, used across most of Meta's web surfaces, and adopted by companies like Figma and Cursor.

Dimension styled-components StyleX
Style generation Runtime — generates CSS during render Compile-time — generates atomic CSS during build
Runtime overhead Style injection, rule parsing on every mount Zero runtime — static CSS classes
Encapsulation Open — any component can be wrapped with styled() Strict — deliberate barriers to external styling
Style resolution CSS specificity-based — conflicts possible Deterministic — last defined style wins predictably
Bundle impact ~12 KB runtime library included in bundle Zero runtime — only compiled CSS output
React Server Components Incompatible — requires DOM access Compatible — static CSS class names only
Maintenance status Maintenance mode — no new features Actively maintained by Meta engineering team
Migration difficulty N/A (source) High — requires codemod for large codebases

Before and After: Code Comparison

The difference between the two approaches is clearest in code. Here's how a typical Linear component changed:

styled-components (Before)

import styled from 'styled-components';
import { Issue } from './types';

const Container = styled.div`
  padding: 12px 16px;
  border-radius: 8px;
  border: 1px solid ${props => props.$active ? '#0f766e' : '#e7e5e4'};
  background: ${props => props.$active ? '#f0fdfa' : '#ffffff'};
  cursor: pointer;
  transition: all 0.2s ease;

  &:hover {
    border-color: #0f766e;
    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  }
`;

const Title = styled.h3`
  font-size: 0.95rem;
  font-weight: 600;
  margin: 0 0 4px 0;
  color: #1c1917;
`;

const Meta = styled.span`
  font-size: 0.8rem;
  color: #78716c;
`;

function IssueCard({ issue, active }: Props) {
  return (
    <Container $active={active}>
      <Title>{issue.title}</Title>
      <Meta>#{issue.number} · {issue.status}</Meta>
    </Container>
  );
}

StyleX (After)

import stylex from '@stylexjs/stylex';

const styles = stylex.create({
  container: {
    padding: '12px 16px',
    borderRadius: 8,
    border: '1px solid #e7e5e4',
    cursor: 'pointer',
    transition: 'all 0.2s ease',
  },
  containerActive: {
    borderColor: '#0f766e',
    backgroundColor: '#f0fdfa',
  },
  title: {
    fontSize: 14,
    fontWeight: 600,
    marginBottom: 4,
    color: '#1c1917',
  },
  meta: {
    fontSize: 13,
    color: '#78716c',
  },
});

const hoverStyles = stylex.create({
  container: {
    borderColor: '#0f766e',
    boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
  },
});

function IssueCard({ issue, active }: Props) {
  return (
    <div
      {...stylex.props(
        styles.container,
        active && styles.containerActive,
      )}
      onMouseEnter={(e) => {
        stylex.merge(hoverStyles.container);
      }}
    >
      <h3 {...stylex.props(styles.title)}>
        {issue.title}
      </h3>
      <span {...stylex.props(styles.meta)}>
        #{issue.number} · {issue.status}
      </span>
    </div>
  );
}

The StyleX version is more explicit about styling — no magic template literals, no dynamic CSS generation at runtime. Every style is declared as a plain object and merged deterministically using stylex.props(). The active state is handled through conditional style composition rather than interpolating a template string.

The Migration at Scale: Building a Codemod

Linear didn't attempt a manual migration — the codebase is too large and too complex. Instead, Kenneth Skovhus built a deterministic codemod: styled-components-to-stylex-codemod.

The complexity of the task is staggering. styled-components uses tagged template literals — essentially a Turing-complete language embedded in CSS strings. Dynamic values, expressions, conditional styles, and props-based calculations create an enormous number of possible patterns. As Skovhus notes, "the number of ways people express the same intent is enormous, and it's too easy to produce output that looks right but subtly isn't."

What makes this even harder: Linear doesn't use a design system. The migration forced them to pay down years of accumulated styling debt — removing flexibility from shared components, tightening APIs, and making components harder to restyle from the outside.

The codemod is now at 500+ pull requests, roughly 100,000 lines of migration tooling, an online playground, cross-file selector handling, and extensive regression coverage. This is a testament to both the scale of modern web applications and the complexity of CSS-in-JS patterns in practice.

What This Means for React Developers

Linear's migration signals a broader shift in the React ecosystem. Compile-time CSS is becoming the default approach for performance-critical applications. Here's what to consider for your own projects:

For Large Applications

If you're running styled-components on a substantial codebase and noticing render-time jank, StyleX (or vanilla-extract) could deliver measurable improvements. The 30% navigation speedup Linear achieved is hard to ignore. However, the migration cost is real — expect to invest in tooling and pay down styling debt.

For New Projects

Starting fresh? Skip runtime CSS-in-JS entirely. StyleX, vanilla-extract, or even CSS Modules with a modern preprocessor offer better performance and fewer long-term maintenance concerns. If you're starting a Next.js project, consider whether you even need a styling library — Tailwind CSS and CSS Modules are first-class citizens.

For Small to Medium Projects

If your application has a few dozen components and page navigation is snappy, the migration cost likely outweighs the benefit. But this is the right time to plan — if you anticipate significant growth, choosing a compile-time solution now saves a major migration later.

The Broader Picture: Runtime vs Compile-Time CSS

The styled-components → StyleX migration is part of a larger industry trend. Runtime CSS-in-JS emerged around 2016 as a reaction against global CSS — it solved real problems (scoping, co-location, dynamic styles). But as applications grew, the runtime cost became visible. Compile-time solutions represent the next evolution: keep the developer experience of CSS-in-JS (co-located styles, component-scoped, dynamic) but eliminate the runtime tax.

For my own projects, I choose the styling approach based on the application's needs. For performance-critical React applications, compile-time CSS provides the best of both worlds. If you're evaluating frameworks or styling systems for a new project, I'd be happy to discuss your requirements and recommend the right approach.

FAQ

Why did Linear migrate from styled-components to StyleX?
Linear migrated for two main reasons: performance (runtime CSS-in-JS has overhead from style generation and rule injection during render) and maintainability (styled-components was put into maintenance mode). StyleX moves style generation to build time and enforces stricter component styling contracts, making the codebase more predictable at scale.
What performance improvements did Linear see after migrating to StyleX?
Linear reported approximately 30% faster render times when navigating between pages. This comes from moving style generation off the critical render path — StyleX compiles styles at build time into atomic CSS classes, so the browser has less JavaScript to parse and execute during page transitions.
What is StyleX and how does it differ from styled-components?
StyleX is Meta's compile-time CSS-in-JS solution. Unlike styled-components which generates and injects styles at runtime, StyleX processes styles during the build step and produces atomic CSS class names. This means zero runtime overhead for style generation, deterministic resolution (no specificity wars), and stricter component encapsulation. StyleX is used across Meta's web surfaces and by companies like Figma and Cursor.
What alternatives did Linear consider before choosing StyleX?
Linear evaluated vanilla-extract (solid static extraction but API felt fragmented) and tested Sanity's optimized fork of styled-components that adds useInsertionEffect support. The fork was described as "a lifeboat, not a long-term plan" — workable short-term but not a sustainable solution.
How did Linear approach the migration at scale?
Linear built a deterministic codemod called styled-components-to-stylex-codemod, now at 500+ PRs and ~100,000 lines of migration tooling. The codemod includes an online playground, cross-file selector handling, and regression coverage. Because Linear doesn't use a design system, much of the migration cost went into tightening component APIs and eliminating styling flexibility.
What are the downsides of migrating from styled-components to StyleX?
StyleX uses a constrained atomic CSS system, making parent-dependent selectors, global selectors, and component restyling through wrappers more difficult. This strictness is intentional — the patterns that are painful to migrate are often the same patterns that made styling harder to reason about at scale. The migration itself requires significant investment in codemod tooling.
Should I migrate my project from styled-components to StyleX in 2026?
For large applications with noticeable render-time overhead, the performance benefits (30% faster navigation) are compelling. For smaller projects, the migration cost likely outweighs the benefit. Consider your options: StyleX, vanilla-extract, or even CSS Modules. If you're planning a new React project, compile-time CSS is the recommended starting point in 2026.

Need Help Choosing Your Styling Strategy?

Choosing a styling system is just one part of frontend architecture. If you're evaluating options for your React application — or planning a migration away from runtime CSS-in-JS — I can help. I'm a full-stack web developer with experience across React, Next.js, and various CSS methodologies. Based in Minsk and working worldwide, I provide free initial consultations to understand your project and recommend the right approach.

Contact

Let's discuss your project

Tell me about your React application — I'll recommend the optimal styling architecture and provide a preliminary estimate. Free of charge.