공개되고 반년, 아무도 덜컥 안 썼다
StyleX는 2023년 12월에 오픈소스로 풀렸다. Meta가 자기네 Facebook, Instagram, WhatsApp, Workplace, Threads 전반에서 쓰는 스타일링 시스템을 그대로 공개한 거다. 발표만 놓고 보면 엄청난 이벤트였다. 수십억 MAU 제품을 받치는 CSS-in-JS 도구가 통째로 열린 거니까.
그런데 이상하게도 공개 직후 반 년 동안 생태계 분위기는 미지근했다. Hacker News에서나 몇 번 회자되다 조용해졌고, 메이저 스타일링 논의에서 StyleX가 기본 선택지로 올라오는 경우는 드물었다. 주변에서도 "한 번 건드려볼까"라고 말하는 사람은 있어도, 실제 프로덕션에 올리는 팀은 거의 없었다.
이게 기술의 문제였냐 하면, 아니었다. 문서도 꽤 정돈돼 있었고, API도 깔끔했다. 적어도 겉보기에는. 그런데 왜 망설였냐.
Meta가 낸 오픈소스의 전례 때문이었다.
Meta가 낸 것들의 세 갈래 길
Meta의 오픈소스 프로젝트를 쭉 세워놓으면 운명이 세 갈래로 나뉜다.
첫 번째 길, React. 세계 표준이 됐다. 이건 모두가 아는 성공 서사인데, 이걸 기본값이라고 생각하면 곤란하다. 예외에 가깝다.
두 번째 길, Flow. Meta가 JavaScript에 타입 시스템을 붙이려고 만든 건데, TypeScript한테 외부 생태계에서 완전히 밀렸다. 2021년 InfoQ 인터뷰에서 Flow 팀은 "Types-First" 모드로 방향을 틀었다고 밝혔다. 외부 범용성보다 Meta 내부 코드베이스 최적화에 집중하겠다는 선언이었다. 기술적으로 죽은 건 아니지만, 바깥 세상과는 사실상 갈라섰다.
세 번째 길, Relay. Meta의 GraphQL 클라이언트. 기술적으로는 탁월한데 너무 복잡해서 대중화에 실패했다. Apollo Client 같은 더 가벼운 대안에 밀렸고, "Meta 규모에선 맞는데 우리한테는 과하다"는 평이 굳어졌다.
StyleX가 나왔을 때 사람들이 떠올린 게 이 세 길이었다. React처럼 될 것이냐, Flow처럼 내부화될 것이냐, Relay처럼 어정쩡하게 표류할 것이냐. 셋 중 어느 쪽일지 아무도 확신하지 못했다. 더 정직하게 말하면, 두 번째나 세 번째일 확률이 더 높아 보였다. Meta가 만든 도구는 늘 Meta 코드베이스를 기준으로 설계되기 때문이다.
테크 리드가 회사를 떠났다
그리고 2024년, 좋지 않은 신호가 들어왔다.
StyleX의 테크 리드였던 Naman Goel이 Meta를 떠나 OpenAI로 옮겼다. 그는 StyleX를 처음 만들고 오픈소스화를 주도한 사람이다. 2025년 11월에 Meta Engineering 블로그가 올린 StyleX 업데이트 포스트에서 그는 "past maintainers" 섹션에 크레딧된다. 과거형이다.
여기에 2024년은 Meta 전반에 레이오프가 도는 해였다. 10월에 Reality Labs, Instagram, WhatsApp 조직에서 대규모 인력 감축이 있었다. StyleX 팀이 직접 영향을 받았다는 공개 보도는 없었지만, 당시 분위기는 "어느 팀이든 내일 없어져도 이상하지 않다"는 쪽이었다.
이 조합이 사람들을 더 주저하게 만들었다. 만든 사람은 떠났고, 모회사는 감축 중이었고, Meta가 쌓아온 "공개하고 내부화시키는" 전례가 있었다. 프로덕션 스타일링 시스템을 갈아끼우는 게 얼마나 큰 결정인지 생각해보면, 이 타이밍에 StyleX에 올인한다는 건 합리적인 선택이 아니었다.
그런데 뜯어보면 잘 만들어놨다
여기서 이야기의 축이 하나 바뀐다. 위기 서사와는 별개로, StyleX의 설계 자체는 이 카테고리에서 가장 앞서 있었다.
가장 먼저 눈에 들어오는 건 atomic CSS 컴파일이다. 빌드 타임에 모든 스타일을 원자 단위 클래스로 쪼개 정적 CSS 파일로 떨어뜨린다. Meta 공식 수치로 CSS 번들 크기가 80% 줄었다. 프로젝트가 커져도 번들이 선형으로 증가하지 않고, 어느 순간부터 평탄해진다는 얘기다.
API는 이렇게 생겼다.
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
base: { fontSize: 16, color: 'grey' },
highlighted: { color: 'rebeccapurple' },
});
<div {...stylex.props(styles.base, styles.highlighted)} />겉보기엔 styled-components나 emotion과 비슷하다. 그런데 동작 방식이 다르다. stylex.props가 반환하는 건 className 문자열이지 런타임에 생성된 스타일 객체가 아니다. 모든 스타일은 이미 빌드 타임에 정적 CSS로 뽑혀 있고, 런타임은 그중 어떤 클래스를 적용할지 고르는 역할만 한다.
그리고 이 라이브러리가 푼 가장 중요한 문제가 하나 있다. 마지막에 적용한 스타일이 이긴다는 규칙이다.
마지막에 쓴 스타일이 이긴다
CSS-in-JS를 써본 사람이면 한 번쯤 겪는 지옥이 있다. 부모 컴포넌트의 스타일과 자식 컴포넌트의 스타일이 합쳐질 때, 뭐가 이길지 예측이 안 되는 문제. CSS의 specificity 규칙이 개입하고, 선언 순서가 꼬이고, 결국 !important로 해결하는 걸로 끝난다.
StyleX의 공식 문서는 이 문제에 대해 아주 명확하게 답한다.
The last style applied always wins. The order in which the styles are defined does not affect the resulting styles, only the order in which they are applied.
stylex.props(styles.base, styles.highlighted)에서 highlighted의 color가 base의 color를 덮는다. 정의 순서가 아니라 적용 순서가 기준이다. 내부적으로는 atomic 클래스 간의 충돌을 컴파일러가 추적해서, 뒤에 오는 것이 항상 이기도록 className을 정리한다.
이게 왜 중요하냐면, CSS-in-JS가 10년 넘게 씨름한 문제를 대부분 사라지게 만들기 때문이다. 조건부 스타일도 그냥 배열로 붙이면 된다.
<div {...stylex.props(
styles.base,
isActive && styles.active,
isHover ? styles.hover : styles.inactive,
)} />falsy 값은 무시되고, truthy인 것들만 순서대로 적용되고, 겹치는 속성은 뒤에 온 것이 이긴다. specificity를 머릿속에 굴릴 필요가 없다. 이것만으로도 도입할 이유가 충분한 팀이 많을 거다.
타입이 닿는 토큰, 그리고 0에 가까운 런타임
StyleX가 기존 CSS-in-JS와 또 다른 지점이 테마와 토큰 시스템이다.
// tokens.stylex.ts
import * as stylex from '@stylexjs/stylex';
const DARK = '@media (prefers-color-scheme: dark)';
export const colors = stylex.defineVars({
primaryText: { default: 'black', [DARK]: 'white' },
accent: { default: 'blue', [DARK]: 'lightblue' },
});defineVars는 CSS 변수를 타입과 함께 선언하는 API다. 파일명이 반드시 .stylex.ts (또는 .stylex.js, .stylex.tsx) 로 끝나야 하고, named export여야 한다. 이 제약이 있어야 컴파일러가 파일을 특별 취급해서, 런타임 의존성 없는 고유한 CSS 변수명으로 뽑아낼 수 있다.
여기서 얻는 게 두 가지 있다. 하나는 디자인 토큰에 TypeScript 타입이 자동으로 붙는다는 거다. 테마 변경, 컬러 오타, 시맨틱 토큰 재명명 같은 작업을 할 때 IDE가 잡아준다. 두 번째는 미디어 쿼리가 변수 정의 안으로 녹아든다는 거다. 다크 모드를 토큰 레벨에서 정의하면, 그걸 쓰는 컴포넌트는 아무것도 몰라도 된다.
런타임에 대해서도 한 번 짚고 가야 한다. StyleX를 "zero runtime"이라고 부르는 건 반만 맞다. 공식 문서는 이렇게 쓴다.
No runtime style injection. Generate a static CSS file at compile-time. Tiny and fast runtime for merging class names.
스타일 생성은 컴파일 타임에 끝나지만, stylex.props가 어떤 className을 실제로 선택할지 결정하는 작은 런타임은 남아있다. 이게 중요한 이유는 React Server Components 시대의 궁합이다. 서버에서 런타임에 CSS를 주입하는 전통적 CSS-in-JS는 RSC 환경에서 문제가 많다. StyleX는 스타일 자체가 이미 정적이기 때문에, 클라이언트에서 className을 머지하는 얇은 런타임만 있으면 된다. RSC, 정적 사이트, 엣지 렌더링 어디서든 무리 없이 돈다.
2025년, 릴리스는 안 멈췄다
2024년 내내 우려가 있었지만, 릴리스 로그를 보면 엄살이었다는 게 드러난다. 2024년엔 0.6.1부터 0.9.3까지 다섯 번의 릴리스가 있었고, 2025년엔 월 1회에 가까운 속도로 0.10부터 0.17까지 나왔다. 이 기간에 shareable media queries, View Transitions, Anchor Positioning, combinator 기반 스타일링 같은 현대 CSS 기능이 꾸준히 붙었다.
외부 도입도 생겼다. Figma, Snowflake, HubSpot이 StyleX를 쓰기 시작했다는 게 2025년 11월 Meta Engineering 포스트에서 확인된다. Meta 내부에선 Facebook, Instagram, WhatsApp, Messenger, Threads가 모두 StyleX 위에서 돈다.
이게 무슨 의미냐면, 처음 우려했던 세 갈래 중 Relay 쪽 길은 아니었다는 거다. React처럼 세계 표준이 된 건 당연히 아니다. 다만 Flow처럼 내부화되지도, Relay처럼 외톨이로 남지도 않았다. Tailwind와 동일한 대중성을 노리는 건 아마 앞으로도 아닐 거고, 그럴 필요도 없다. 대규모 프로덕트, 엄격한 타입, RSC를 쓰는 팀에겐 이미 가장 합리적인 선택지다.
수렴점에 가까워지는 카테고리
스타일링 라이브러리의 역사는 꽤 어지러웠다. 인라인 스타일, CSS Modules, styled-components, emotion, vanilla-extract, Tailwind, Panda CSS. 각자 해결한 문제도 다르고 포기한 것도 다르다.
StyleX는 이 계보에서 특정 문제 하나를 더 푼다기보다는, 지금까지의 합의점들을 한 번에 묶어낸다. 컴파일 타임 추출, 원자 단위 번들, 타입 안전성, 예측 가능한 합성 규칙, 테마 시스템, RSC 호환. 발명이라기보다는 수렴이다. 여러 라이브러리가 각자 증명해온 좋은 아이디어를 한 지붕 아래 모아놓은 결과물.
그래서 StyleX를 "이 카테고리의 마지막 답"이라고 말하긴 어렵다. Promise처럼 언어 표준으로 올라서는 일은 아마 일어나지 않을 거다. 다만 CSS-in-JS라는 카테고리 안에서 설계가 수렴할 지점에 가장 가까이 가 있는 건 사실이다. 2024년엔 이 판단을 내리기 어려웠고, 2025년이 돼서야 결과가 보였다.
한 번 식어본 기술이 다시 올라오는 경우는 흔치 않다. StyleX는 그 흔치 않은 경우에 들어 있다.