本文档包含对所有当前CSS-IN-JS解决方案的深入分析,该解决方案支持服务器端渲染和打字稿。
我们将用于比较的基线参考是CSS模块方法。
我们将Next.js用作建筑资源的SSR框架。
最后的重要方面是带有完整标题支持的类型安全。
?上次更新: 2021年8月
?要获得较短的概述,您可以在CSS技巧上查看文章:
https://css-tricks.com/a-thorough-analysis-of-css-in-js/
?如果您喜欢视频,则可以从Ngpartycz结帐我的演讲:
https://www.youtube.com/watch?v=c7uwghrax9a
请在得出结论之前查看我们的目标和免责声明。
CSS语言和CSS模块有一些局限性,尤其是在我们想具有类型安全代码的情况下。这些限制中的一些具有改变的解决方案,其他限制只是令人讨厌或不理想:
样式不能与组件共同置于
在创作许多小型组件时,这可能会令人沮丧,但这并不是一个破坏交易的人。但是,在component.js文件和component.css文件,搜索给定的类名称以及无法轻松“转到样式定义”之间的back and Forth的经验是一个重要的生产力缺陷。
样式伪和媒体查询需要选择器重复
另一个令人沮丧的事实是,在定义伪类和元素或媒体查询时需要复制我们的CSS课程。我们可以使用支持&父母选择器的CSS预处理器(例如Sass,Limes或Stylus)来克服这些限制,从而实现上下文样式。
. button {}
/* duplicated selector declaration for pseudo classes/elements */
. button : hover {}
. button :: after {}
@media ( min-width : 640 px ) {
/* duplicated selector declaration inside media queries */
. button {}
}样式的使用与他们的定义断开
我们没有CSS模块的IntelliSense,其中CSS类在component.css文件中定义了哪些类别,使Copy-Paste成为必需的工具,从而降低DX。由于缺乏安全性,这也使重构非常繁琐。
在CSS中使用类型安全的设计令牌是非平凡的
CSS中不能直接使用JS/TS中定义的任何设计令牌。
这个问题至少有2个解决方法,它们都不优雅:
.module.css中使用它们时,我们仍然不会获得任何IntelliSense或类型安全。.css文件。我们正在寻找此分析的具体目标:
更具体地说,我们想体验有关以下情况的各种CSS-IN-JS解决方案的使用:
props (又称组件变体)或用户输入的动态样式该分析旨在是客观且未经公开的:
?您在这里找不到什么?
?您会在这里找到什么?
图书馆均不以任何特定顺序呈现。如果您对CSS-In-JS的简短历史感兴趣,则应查看Max Stoiber的CSS-In-Js Indight Full Talk的过去,现在和未来。
| 1。共同定位 | 2。DX | tag` ` | 4。 { } | 5。TS | 6。 & CTX | 7。筑巢 | 8。主题 | .css | 10。 <style> | 11。原子 | className | 13。 <Styled /> | css道具 | 15。不可知论 | 16。页面大小三角洲 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CSS模块 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - | |||||||||
| 风格的JSX | ✅ | ? | ✅ | ? | ✅ | ✅ | ✅ | +2.8 kB / +12.0 kB | ||||||||
| 样式的组件 | ✅ | ? | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +13.4 kB / +39.0 kB | ||||
| 情感 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +6.5 kB / +20.0 kB | ||
| 打字机 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ? | ✅ | ✅ | ✅ | +2.1 kB / +8.0 kB | |||||
| 费拉 | ✅ | ? | ? | ✅ | ? | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +11.9 kB / +43.0 kB | |||
| 针迹 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ? | ✅ | +5.3 kB / +17.0 kB | |||
| JSS | ✅ | ✅ | ? | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ? | ✅ | +18.2 kB / +60.0 kB | |||
| 戈贝 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ? | ✅ | +1.1 kB / +4.0 kB | ||
| 编译 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ? | ✅ | ✅ | +3.5 kB / +9.0 kB | |||
| Linaria | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +2.7 kB / +6.0 kB | ||||
| 香草提取 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ? | ✅ | ✅ | +0.0 kB / -2.0 kB |
与组件在同一文件中定义样式的能力。请注意,如果我们喜欢它,我们还可以将样式提取到单独的文件中并导入它们。
⬆️概述
指开发人员体验,其中包括两个主要方面:
⬆️概述
tag` ` (标记模板)支持将样式定义为字符串,使用ES标记模板:
kebab-case用作属性名称;string ;⬆️概述
{ } (对象样式)支持将样式定义为对象,使用普通的JavaScript对象:
camelCase用于属性名称,就像我们在React Native中所做的一样;⬆️概述
打字稿支持,无论是内置的还是@types软件包,其中应包括:
Props Generics,如果适用(定义动态样式时,可以获取对组件类型类型的类型访问);⬆️概述
& CTX(上下文样式)支持上下文样式,使我们可以轻松地定义伪类,元素和媒体查询,而无需按照普通CSS的要求重复选择器:
& Parent Selector;⬆️概述
支持任意嵌套的选择器:
⬆️概述
内置支持设计系统的主题或管理令牌。
我们尚未测试此功能,因此我们只记下图书馆在文档中表达支持的笔记。
⬆️概述
.css (静态CSS提取)定义样式被提取为静态.css文件:
⬆️概述
<style>标签定义的样式被注入文档的<head>中的<style>标签:
⬆️概述
产生原子CSS类的能力,从而提高样式可重复性并减少重复:
⬆️概述
className库API返回一个我们必须添加到组件或元素的string ;
⬆️概述
<Styled /> API创建一个包装器(或Styled )组件,其中包括生成的className :
StyledButton或StyledList之类的组件,而不是button_styles或list_styles等常数,因此,关于命名它几乎是相同的事情;⬆️概述
css道具允许使用特殊的css道具传递样式,类似我们将如何定义内联样式,但是库在幕后生成了唯一的CSS类名称:
⬆️概述
允许无需使用或使用任何框架。一些库是专门为反应而构建的。
注意:一些图书馆(例如针迹或情感文档)仅反应用法,尽管它们具有框架不可知的核心。
⬆️概述
与CSS模块相比,KB的总页面尺寸差异(转移的GZZEND和MINIFIED / UNIFIED / UNIFECTRESS和MINIFIED),使用Next.js:整个索引页面生产:
注意:所有构建均使用Next.JS 11.1.0完成,并且值取自Chrome DevTools网络选项卡,通过网络与资源大小传输。
⬆️概述
以下观察结果适用于所有解决方案(除次要例外)。
仅在特定路线中使用的组件只会捆绑在该路线上。这是Next.js执行开箱即用的东西。
所有解决方案都提供了一种定义全球样式的方法,其中一些具有专用API。
所有解决方案均提供服务器端渲染支持,并且易于与Next.js集成。
所有解决方案都会自动添加特定于供应商的前缀。
所有解决方案都会产生独特的类名称,例如CSS模块。用于生成这些名称的算法在库之间有很大不同:
.heading来自Card组件的head样式始终具有.Card_heading_h7Ys5 hash);.heading-0-2-1 , .input-0-2-2 )或字母字母( a, b, c, ... aa, ab, ac等)上增加一个数字(.head-0-2-1,.input-0-2-2),使这种方法具有更大的性能,但导致非势力的类名称(无法弄清楚这是否具有任何潜在的草图或没有潜在的草图); radium&Glamor使用的任何解决方案均未产生内联样式,这是一种较旧的方法。该方法的性能低于CSS类,也不建议作为定义样式的主要方法。这也意味着使用JS事件处理程序来触发伪类,因为内联样式不支持它们。显然,如今的所有现代解决方案都摆脱了这种方法。
所有解决方案都支持您需要的大多数CSS属性:伪类和元素,媒体查询和关键帧是我们测试的。
大多数解决方案本身都可以在SSR期间“提取关键CSS” 。请注意,正如我们最初认为的那样,这并不是指上述关键CSS提取。
他们实际做什么:
使用100%静态CSS,实际上不会有任何好处。随着动态页面在服务器上呈现很少的元素,并且大多数组件在客户端上动态渲染,因此收益会增加。
例外:使用静态CSS提取的库。
了解这些功能如何影响核心的网络生命力和性能指标是一个非常重要的因素,并且样式向客户交付的方式可能会产生最大的影响,因此让我们详细分析这一点。
另外,我们需要考虑2种不同的方案:
.js , .css ,媒体等); .css文件提取生成.css静态文件的解决方案通常将其包含在页面的<head>中的<link> tag(s),这基本上是渲染阻断资源。这极大地影响了FCP , LCP和随后的任何其他指标。
?空缓存
如果用户有一个空的缓存,则需要发生以下情况,对FCP和LCP产生负面影响:
<body>的任何渲染,即使整个HTML也可以提前提前获取。的确,您可以在其他其他<head>资源(附加.css或.js文件)中获取,但这通常是一个不好的做法。
?完整的缓存
但是,在随后的访问中,整个.css资源将被缓存,因此FCP和LCP将受到积极影响。
关键点
当以下内容时,该解决方案似乎更适合
.css文件,该文件在访问其他页面时可能会被缓存;<style>标签注入样式在SSR期间,样式将在页面的<head> >中添加为<style> >标签。请记住,这些通常不包含页面所需的所有样式,因为大多数库执行关键的CSS提取,因此这些styles通常应比以前讨论的整个.css静态文件小。
?空缓存
因为我们将少量的CSS字节运送,并且它们在.html文件中内衬,因此这将导致FCP和LCP更快:
.css文件的其他请求,因此浏览器不会被阻止。.js文件请求移至文档末尾,则<head>将不会执行任何请求,因此渲染将超快发生;.css提取不需要:.js文件与组件一起捆绑在一起(其中包括所有已经在<style> tag +其他的关键CSS); ?完整的缓存
当用户的缓存已满时,额外的.js文件将不需要获取,因为它们已经被缓存。
但是,如果该页面被划分,则将在文档的<style>标签中渲染的夹具关键的CSS再次下载,除非我们处理也可以缓存的静态HTML,否则我们可以处理基础架构上的HTML缓存。
但是,默认情况下,无论是否缓存,我们都会在HTTP请求上运送额外的字节。
关键点
当以下内容时,该解决方案似乎更适合
大多数解决方案都说它们删除了未使用的代码/样式。这只是一半。
确实很难积累未使用的代码,尤其是如果将其与过去十年前写的普通.css文件进行比较。但是,与CSS模块相比,差异并不大。提供定义任意选择器或嵌套样式的选项的任何解决方案都将捆绑它们,无论它们是否在我们的组件内使用。我们设法将未使用的SSR样式运送出所有经过测试的解决方案。
很难实现真实和完整未使用的代码删除,因为CSS语法没有类型检查,也不可静态分析。同样,组件的动态性质使得在某些情况下实际上是不可能的,尤其是当标记被动态渲染时:
& span :后代元素;&:nth-child() :某些伪选择器;& .bg-${color} :动态选择器;.parent &基本上,当我们删除组件时,我们得到的是删除代码,否则我们不再导入它。这是隐式的行为,因为样式是组件的直接依赖性。当组件消失时,其样式也是如此。
有两种方法可以将CSS注入DOM并从JavaScript进行更新:
<style>标签这种方法意味着在DOM中添加一个或多个<style>标签(在<head>中或在<body>中的某个地方),使用.appendChild()添加<style> node(s),此外dextContent,.innerhtml,.innerhtml以更新<style> style> s s)。
<style>标签并更新其整个内容,当我们实际上仅更改一小部分CSS规则时,更新整个DOM可能会很慢。DEVELOPMENT模式下使用此解决方案,因为它提供了更好的调试体验;PRODUCTION ; CSSStyleSheet API该方法首先使用JSS使用,使用CSSStyleSheet.insertRule()将CSS规则直接注入CSSOM 。
<style> tag;<style>标签。$0获取访问它(或使用DOM API以任何其他方式获得参考);<style>标签上的access .sheet.cssRules查看其中包含的CSS规则;PRODUCTION中使用此方法。DEVELOPMENT模式; 如果相同的组件是通过2种不同的路由导入的,则将两次发送给客户端。在我们的情况下,这无疑是捆绑器/构建系统的限制。
在Next.js中,在路线级别上进行代码拆分作品,将特定路线所需的所有组件捆绑在一起,但是根据他们的官方博客和Web.dev,如果在超过50%的页面中使用了一个组件,则应包含在commons Bundle中。但是,在我们的示例中,我们有2页,每个页面都会导入Button组件,并且它包含在每个页面捆绑包中,而不是在commons Bundle中。由于样式所需的代码与组件捆绑在一起,因此此限制也会影响样式,因此值得记住。
这是一种良好,成熟和扎实的方法。毫无疑问,这是对BEM,SMACC,OOCS或任何其他可扩展的CSS方法的巨大改进,以结构和组织我们的CSS,尤其是在基于组件的应用中。
于2015年推出|返回概述
✅上下文感知代码完成
✅框架不可知论
没有样式/组件共同设置
没有打字稿支持
没有原子CSS
没有主题支持
样式定义方法
样式嵌套
样式应用方法
classNamestyled组件css道具样式输出
.css文件提取<style>标签注射在比较所有以下CSS-IN-JS解决方案时,这是我们考虑的基线。结帐,以更好地了解我们要填写的这种方法的局限性。
| 转移 / gzz | 未压缩 | |
|---|---|---|
| 索引页面大小 | 76.7 kb | 233 kb |
Page Size First Load JS
┌ ○ / 2.19 kB 68.7 kB
├ └ css/1d1f8eb014b85b65feee.css 450 B
├ /_app 0 B 66.5 kB
├ ○ /404 194 B 66.7 kB
└ ○ /other 744 B 67.2 kB
└ css/1c8bc5a96764df6b92b4.css 481 B
+ First Load JS shared by all 66.5 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.40892d.js 555 B
├ chunks/webpack.ddd010.js 822 B
└ css/a92bf2d3acbab964f6ac.css 319 B
非常简单的解决方案,没有专用网站用于文档,所有内容都在Github上。它不受欢迎,但它是Next.js中的内置解决方案。
版本: 4.0 |由Vercel维护|于2017年推出|查看文档| ...返回概述
✅样式/组件共处
?上下文感知的代码完成:要获得语法突出显示和代码完成,需要编辑器扩展程序
?打字稿支持: @types可以安装额外,但是API太小了,不需要TS
没有原子CSS
没有主题支持
不是框架不可知
样式定义方法
样式嵌套
样式应用方法
classNamestyled组件css道具样式输出
.css文件提取<style>标签注射elements ,并且它为它们生成了独特的类名称(不确定这是一个好练习)总体而言,我们感觉就像写普通CSS一样,具有能够与组件一起定义样式的额外好处,因此我们不需要其他.css文件。确实,这是库的理念:支持组件文件中的CSS语法。我们可以使用带有字符串插值的功能的任何JS/TS常数。使用动态样式非常容易,因为它最终是简单的JavaScript。我们以非常低的价格获得所有这些好处,开销很小。
缺点是写普通CSS的总体经验。不嵌套支持伪类的类别/元素和媒体查询变得非常笨拙。
| 转移 / gzz | 未压缩 | |
|---|---|---|
| 索引页面大小 | 79.5 kb | 245 kb |
| vs. CSS模块 | +2.8 kb | +12 kb |
Page Size First Load JS
┌ ○ / 2.65 kB 72.6 kB
├ /_app 0 B 70 kB
├ ○ /404 194 B 70.2 kB
└ ○ /other 1.18 kB 71.2 kB
+ First Load JS shared by all 70 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.a4b061.js 4.12 kB
└ chunks/webpack.61f1b6.js 778 B
可以肯定的是最受欢迎和最成熟的解决方案之一,具有良好的文档。它使用标记模板默认定义样式,但也可以使用对象。它还普及了styled组件方法,该方法与定义的样式一起创建了新组件。
版本: 5.3 |由Max Stoiber等维护|于2016年推出|查看文档| ...返回概述
✅样式/组件共处
✅打字稿支持: @types必须通过肯定地安装
✅内置主题支持
✅框架不可知论
?上下文感知的代码完成:需要编辑器扩展程序/插件
没有原子CSS
样式定义方法
样式嵌套
样式应用方法
classNamestyled组件css道具样式输出
.css文件提取<style>标签注射Props变化的动态样式混合等)样式组件使用styled方法提供了一种新型的样式组件方法,该方法创建了包括定义样式的新组件。我们不想写CSS,因此来自CSS模块,我们必须学习一种新的,更具程序性的方式来定义样式。因为它允许string和object语法,所以它是一个非常挠性的解决方案,既可以迁移我们的现有样式,又用于从头开始启动项目。此外,维护者在与该领域的大多数创新保持一致方面做得很好。
但是,在采用它之前,我们必须意识到,它的捆绑尺寸有一定的成本。
| 转移 / gzz | 未压缩 | |
|---|---|---|
| 索引页面大小 | 90.1 kb | 272 kb |
| vs. CSS模块 | +13.4 kb | +39 kb |
Page Size First Load JS
┌ ○ / 2.52 kB 83.1 kB
├ /_app 0 B 80.6 kB
├ ○ /404 194 B 80.8 kB
└ ○ /other 1.06 kB 81.7 kB
+ First Load JS shared by all 80.6 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.731ace.js 14.7 kB
└ chunks/webpack.ddd010.js 822 B
可能是最全面,最完整和统一的解决方案。详细的文档完整地使用打字稿构建,看起来很成熟,具有丰富的功能且维护良好。
版本: 11.4 |由米切尔·汉密尔顿(Mitchell Hamilton)及其他人维护|于2017年推出|查看文档| ...返回概述
✅样式/组件共处
✅打字稿支持
✅内置主题支持
✅上下文感知的代码完成:对于使用styled组件方法,需要附加编辑器插件
✅框架不可知论
没有原子CSS
样式定义方法
样式嵌套
样式应用方法
className (使用 @emotion/css)styled组件css道具样式输出
.css文件提取<style>标签注射css Prop在开发过程中提供了出色的人体工程学,但是它似乎是一种较新的方法,基于React 17新的jsx变换,并且配置它并不琐碎,在您的设置上有所不同,并暗示了某些样板(这应该很快变化并变得更加容易) styled方法将在我们的捆绑包中添加3 kB ,因为它是从单独的包装中导入的css Prop& styled组件) 总体情绪看起来是一种非常扎实和灵活的方法。新颖的css道具方法为开发人员提供了出色的人体工程学。使用动态样式和打字稿非常容易且直观。在定义样式时支持strings和objects ,从普通CSS迁移或从头开始时可以很容易地使用它。捆绑开销不是可以忽略不计,但绝对比其他解决方案小得多,尤其是如果您考虑其提供的丰富功能。
看来它没有专门关注性能,而是更多地关注开发人员的体验。它看起来像是一个完美的“全面”解决方案。
| 转移 / gzz | 未压缩 | |
|---|---|---|
| 索引页面大小 | 83.2 kb | 253 kb |
| vs. CSS模块 | +6.5 kb | +20 kb |
Page Size First Load JS
┌ ○ / 2.5 kB 76.4 kB
├ /_app 0 B 73.9 kB
├ ○ /404 194 B 74.1 kB
└ ○ /other 1.07 kB 74.9 kB
+ First Load JS shared by all 73.9 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.6cb893.js 23.3 kB
├ chunks/pages/_app.b6d380.js 7.68 kB
└ chunks/webpack.ddd010.js 822 B
最少的库,仅专注于类型检查。 It is framework agnostic, that's why it doesn't have a special API for handling dynamic styles. There are React wrappers available, but the typings feels a bit convoluted.
Version: 2.1 | Maintained by Basarat | Launched in 2017 | View Docs | ... back to Overview
✅ Styles/Component co-location
✅ TypeScript support
✅ Context-aware code completion
✅ Framework agnostic
? Built-in Theming support : uses TS namespaces to define theming, which is not a recommended TS feature even by the author himself, or by TS core team member Orta Therox.
No Atomic CSS
Styles definition method(s)
Styles nesting
Styles apply method(s)
classNamestyled componentcss propStyles output
.css file extraction<style> tag injection <style> tag with all the styles, and replaces it on update, and apparently it doesn't use insertRule() , not even in production builds, which might be an important performance drawback in large & highly dynamic UIs Overall TypeStyle seems a minimal library, relatively easy to adopt because we don't have to rewrite our components, thanks to the classic className approach. However we do have to rewrite our styles, because of the Style Object syntax. We didn't feel like writting CSS, so there is a learning curve we need to climb.
With Next.js or React in general we don't get much value out-of-the-box, so we still need to perform a lot of manual work. The external react-typestyle binding doesn't support hooks, it seems to be an abandoned project and the typings are too convoluted to be considered an elegant solution.
| Transferred / gzipped | Uncompressed | |
|---|---|---|
| Index page size | 78.8 kB | 241 kB |
| vs. CSS Modules | +2.1 kB | +8 kB |
Page Size First Load JS
┌ ○ / 2.44 kB 72.1 kB
├ /_app 0 B 69.7 kB
├ ○ /404 194 B 69.9 kB
└ ○ /other 975 B 70.7 kB
+ First Load JS shared by all 69.7 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.5b0422.js 3.81 kB
└ chunks/webpack.61f1b6.js 778 B
It appears to be a mature solution, with quite a number of users. The API is intuitive and very easy to use, great integration for React using hooks.
Version: 11.6 | Maintained by Robin Weser | Launched in 2016 | View Docs | ... back to Overview
✅ Styles/Component co-location
✅ Built-in Theming support
✅ Atomic CSS
✅ Framework agnostic
? TypeScript support : it exposes Flow types, which work ok, from our (limited) experience
? Context-aware code completion : styles defined outside the component require explicit typing to get code completion
Styles definition method(s)
Styles nesting
Styles apply method(s)
classNamestyled componentcss propStyles output
.css file extraction<style> tag injection a , b , ...)Fela looks to be a mature solution, with active development. It introduces 2 great features which we enjoyed a lot. The first one is the basic principle that "Style as a Function of State" which makes working with dynamic styles feel super natural and integrates perfectly with React's mindset. The second is atomic CSS class names, which should potentially scale great when used in large applications.
The lack of TS support however is a bummer, considering we're looking for a fully type-safe solution. Also, the scaling benefits of atomic CSS should be measured against the library bundle size.
| Transferred / gzipped | Uncompressed | |
|---|---|---|
| Index page size | 88.6 kB | 276 kB |
| vs. CSS Modules | +11.9 kB | +43 kB |
Page Size First Load JS
┌ ○ / 2.84 kB 81.7 kB
├ /_app 0 B 78.9 kB
├ ○ /404 194 B 79 kB
└ ○ /other 1.43 kB 80.3 kB
+ First Load JS shared by all 78.9 kB
├ chunks/framework.2191d1.js 42.4 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.32bc1d.js 12.6 kB
└ chunks/webpack.ddd010.js 822 B
Very young library, solid, modern and well-thought-out solution. The overall experience is just great, full TS support, a lot of other useful features baked in the lib.
Version: 0.2.5 (beta) | Maintained by Modulz | Launched in 2020 | View Docs | ... back to Overview
✅ Styles/Component co-location
✅ TypeScript support
✅ Context-aware code completion
✅ Built-in Theming support
✅ Framework agnostic : (available with @stitches/core )
Atomic CSS
Styles definition method(s)
Styles nesting
Styles apply method(s)
classNamestyled componentcss prop (used only to override styled components)Styles output
.css file extraction<style> tag injection variants (for predefined styles), or styles created inside the component to get access to the propsStitches is probably the most modern solution to this date, with full out-of-the-box support for TS. Without a doubt, they took some of the best features from other solutions and put them together for an awesome development experience. The first thing that impressed us was definitely the documentation. The second, is the API they expose which is close to top-notch. The features they provide are not huge in quantity, but are very well-thought-out.
However, we cannot ignore the fact that it's still in beta. Also, the authors identify it as "near-zero runtime" , but at +9 kB gzipped it's debatable.
| Transferred / gzipped | Uncompressed | |
|---|---|---|
| Index page size | 82.0 kB | 250 kB |
| vs. CSS Modules | +5.3 kB | +17 kB |
Page Size First Load JS
┌ ○ / 2.43 kB 75.2 kB
├ /_app 0 B 72.8 kB
├ ○ /404 194 B 73 kB
└ ○ /other 984 B 73.8 kB
+ First Load JS shared by all 72.8 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.ff82f0.js 6.93 kB
└ chunks/webpack.61f1b6.js 778 B
Probably the grandaddy around here, JSS is a very mature solution being the first of them, and still being maintained. The API is intuitive and very easy to use, great integration for React using hooks.
Version: 10.7 | Maintained by Oleg Isonen and others | Launched in 2014 | View Docs | ... back to Overview
✅ Styles/Component co-location
✅ Built-in Theming support
✅ Framework agnostic
✅ TypeScript support
✅ Context-aware code completion
No Atomic CSS
Styles definition method(s)
Styles nesting
Styles apply method(s)
classNamestyled component (available with additional plugin)css propStyles output
.css file extraction<style> tag injection react-jss package, which is used with React/Next.js, depends on jss-preset-default, which includes many plugins by default, so you don't need to manually add some of the plugins;react-jss uses className by default. There's also styled-jss that uses Styled Components approach, but it has no types, and couldn't make it work on top of react-jss ;injectSheet API (or we couldn't find it anywhere);The API is similar in many ways to React Native StyleSheets, while the hooks helper allows for easy dynamic styles definition. There are many plugins that can add a lot of features to the core functionality, but attention must be payed to the total bundle size, which is significant even with the bare minimum only.
Also, being the first CSS-in-JS solution built, it lacks many of the modern features that focuses on developer experience.
| Transferred / gzipped | Uncompressed | |
|---|---|---|
| Index page size | 94.9 kB | 293 kB |
| vs. CSS Modules | +18.2 kB | +60 kB |
Page Size First Load JS
┌ ○ / 2.45 kB 88 kB
├ /_app 0 B 85.6 kB
├ ○ /404 194 B 85.8 kB
└ ○ /other 992 B 86.6 kB
+ First Load JS shared by all 85.6 kB
├ chunks/framework.2191d1.js 42.4 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.5f0007.js 19.2 kB
└ chunks/webpack.9c89cc.js 956 B
A very light-weight solution, with a loads of features.
Version: 2.0 | Maintained by Cristian Bote | Launched in 2019 | View Docs | ... back to Overview
✅ Styles/Component co-location
✅ Built-in Theming support
✅ TypeScript support
✅ Context-aware code completion
✅ Framework agnostic
No Atomic CSS
Styles definition method(s)
Styles nesting
Styles apply method(s)
classNamestyled component ( see details below )css prop ( is supported, but requires a separate babel plugin )Styles output
.css file extraction<style> tag injection <style> tag with all the styles, and appends to it on update, and apparently it doesn't use insertRule() , not even in production builds, which might be an important performance drawback in large & highly dynamic UIs Looking at Goober you cannot ask yourself what kind of magic did Cristian Bote do to fit all the features inside this tiny library. It is really mind blowing. It is marketed as being "less than 1KB" , which is not entirely accurate, but still... it's the smallest library we've tested.
| Transferred / gzipped | Uncompressed | |
|---|---|---|
| Index page size | 77.8 kB | 237 kB |
| vs. CSS Modules | +1.1 kB | +4 kB |
Page Size First Load JS
┌ ○ / 2.77 kB 71.1 kB
├ /_app 0 B 68.3 kB
├ ○ /404 194 B 68.5 kB
└ ○ /other 2.39 kB 70.7 kB
+ First Load JS shared by all 68.3 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.5ee014.js 2.42 kB
└ chunks/webpack.61f1b6.js 778 B
A rather new library, having the huge Atlassian platform supporting and probably using it. Many existing features, even more in development, or planned for development.
Version: 0.6 | Maintained by Atlassian | Launched in 2020 | View Docs | ... back to Overview
✅ Styles/Component co-location
✅ TypeScript support
✅ Context-aware code completion
✅ Atomic CSS
Not Framework agnostic
No Built-in Theming support (at least at the moment, but it is planned)
Styles definition method(s)
Styles nesting
Styles apply method(s)
className (only supported with a custom ClassNames component)styled componentcss propStyles output
.css file extraction (currently under development, will be shipped in 2021)<style> tag injection css prop is seamless and trivial, not requiring any special setup (unlike Emotion) <head> during SSR - instead they are placed right before the element using them in the <body> , which could potentially provide slightly faster Paint metrics, such as FCP, or LCP, because the browser can start rendering the body faster and incrementally, not waiting for the entire block of styles to be parsedClassNames API, which enables us to apply styles as class name strings, is a bit convoluted and weird at first sight. Compiled is a very promising library. Considering that it offers both atomic CSS, and it plans to support static .css extraction, with excellent TypeScript support and style co-location, it would be quite unique (having only style9 as a direct competitor).
Also, we cannot ignore that is has Atlassian supporting its development, which puts a (slightly) bigger weight on the confidence level.
The total bundle overhead is pretty small, the runtime library being quite light-weight. With static .css file extraction, this could potentially become even smaller.
| Transferred / gzipped | Uncompressed | |
|---|---|---|
| Index page size | 80.2 kB | 242 kB |
| vs. CSS Modules | +3.5 kB | +9 kB |
Page Size First Load JS
┌ ○ / 2.11 kB 71.8 kB
├ /_app 0 B 66.5 kB
├ ○ /404 194 B 66.7 kB
└ ○ /other 888 B 70.6 kB
+ First Load JS shared by all 66.5 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.ebe095.js 576 B
├ chunks/webpack.ddd010.js 822 B
└ css/a92bf2d3acbab964f6ac.css 319 B
Linaria is all about static CSS extraction and avoiding any runtime overhead.
Version: 3.0 (beta) | Maintained by Callstack | Launched in 2018 | View Docs | ... back to Overview
✅ Styles/Component co-location
✅ TypeScript support
✅ Context-aware code completion
✅ Framework agnostic
✅ Built-in Theming support
No Atomic CSS
Styles definition method(s)
Styles nesting
Styles apply method(s)
classNamestyled componentcss propStyles output
.css file extraction<style> tag injection Linaria is highly inspired from Astroturf, combining various features from other libraries.
Version 3 is currently in Beta, not sure what the changelog is compared to v2. It's still in development by the React/Native geeks at Callstack.io , but we couldn't find which of the big players use it in production.
It seems to have a slightly larger overall page size ( 2.9 KB ), but we didn't investigate where does this come from. Also, there's an open question if this overhead is fixed or if it scales.
PS: thanks to Daniil Petrov for his PR with the Next.js integration
| Transferred / gzipped | Uncompressed | |
|---|---|---|
| Index page size | 79.4 kB | 239 kB |
| vs. CSS Modules | +2.7 kB | +6 kB |
Page Size First Load JS
┌ ○ / 4.99 kB 71.5 kB
├ └ css/16f3e95ede28dcc048f2.css 423 B
├ /_app 0 B 66.5 kB
├ ○ /404 194 B 66.7 kB
└ ○ /other 3.59 kB 70.1 kB
└ css/3064299bff08067ec7dd.css 427 B
+ First Load JS shared by all 66.5 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.b2b078.js 23.1 kB
├ chunks/pages/_app.98e8c3.js 598 B
├ chunks/webpack.ddd010.js 822 B
└ css/7739287c04a618ea0c54.css 295 B
Modern solution with great TypeScript integration and no runtime overhead. It's pretty minimal in its features, straightforward and opinionated. Everything is processed at compile time, and it generates static CSS files. Successor of Treat, also be called "Treat v3", is developed and maintained by the same authors.
Version: 1.2 | Maintained by Seek OSS | Launched in 2021 | View Docs | ... back to Overview
✅ TypeScript support
✅ Built-in Theming support
✅ Context-aware code completion
✅ Framework agnostic
? Atomic CSS : can be achieved with Sprinkles
No Styles/Component co-location : styles must be placed in an external .css.ts file
Styles definition method(s)
Styles nesting
Styles apply method(s)
classNamestyled componentcss propStyles output
.css file extraction<style> tag injection & > span ), which might be seen as a downside, but it actually discourages bad-practices like specificity wars , which should be avoided when scaling CSS (however, this is impossible to be statically type-checked without pattern matching , so it will throw a runtime exception)variants based on predefined types, or inline styles for user-defined styles We felt a lot like using CSS Modules: we need an external file for styles, we place the styles on the elements using className , we handle dynamic styles with inline styles , etc. However, we don't write CSS, and the overall experience with TypeScript support is magnificent, because everything is typed, so we don't do any copy-paste . Error messages are very helpful in guiding us when we do something we're not supposed to do.
vanilla-extract is built with restrictions in mind, with a strong user-centric focus, balacing the developer experience with solid TypeScript support. It's also worth mentioning that Mark Dalgleish, co-author of CSS Modules, works at Seek and he's also a contributor.
The authors vision is to think of vanilla-extract as a low-level utility for building higher-level frameworks, which will probably happen in the future.
| Transferred / gzipped | Uncompressed | |
|---|---|---|
| Index page size | 76.7 kB | 231 kB |
| vs. CSS Modules | +0.0 kB | -2 kB |
Page Size First Load JS
┌ ○ / 2.09 kB 68.5 kB
├ └ css/37c023369f5e1762e423.css 370 B
├ /_app 0 B 66.4 kB
├ ○ /404 194 B 66.6 kB
└ ○ /other 611 B 67 kB
└ css/a56b9d05c6da35ff125f.css 386 B
+ First Load JS shared by all 66.4 kB
├ chunks/framework.895f06.js 42 kB
├ chunks/main.700159.js 23.1 kB
├ chunks/pages/_app.bfd136.js 565 B
├ chunks/webpack.61f1b6.js 778 B
└ css/23b89d9ef0ca05e4b917.css 286 B
We know there are a lot of other libraries out there, besides the ones covered above. We're only covered the ones that have support for React , support for SSR , an easy integration with Next.js , good documentation and a sense of ongoing support and maintenance . Please checkout our goals.
Treat was initially included in the analysis with v1.6, but removed for a few reasons:
The main difference between vanilla-extract and Treat is that the latter supports IE and legacy browsers as well.
Style9 is a new library, inspired by Facebook's own CSS-in-JS solution called stylex. Style9 is unique because it's the only open source library that supports both .css static extraction + atomic CSS, and/or styles co-location. It has TS support and easy to integrate with Next.js.
However, it has quite a few limitations (at least as of Feb 2021) that makes it practically unusable in a real production application that we would want to scale, both in code & team size:
Enum or POJO , only constant primitives are supported, which is a big deal breaker ;classNames lib, but not dynamically/computed/expression based;Some upsides:
As a conclusion, it wants to be a powerful solution with very interesting and unique set of features, but it's not mature yet. As far as we see, it's currently mostly designed towards more static solutions. Dynamic styling seems to be difficult to handle, at least for the moment.
Not an actual CSS-in-JS library, more like a replacement for traditional CSS styling. It uses atomic CSS classes (some of them having multiple properties) that we attach to html elements. We don't write CSS, instead we use a different DSL to specify styles, pseudo classes, media queries, etc.
The reason we didn't include it in our thorough review is because it doesn't fully meet our goals:
.ts files to include them in tailwind.config (cannot import any file, cannot require .ts )tailwind.config directly offers no type-safety when importing it, or using resolveConfigrounded , place-self/content , divide , ring )::after pseudo elements are trickySome upsides:
tailwind.configTailwind seems to be more than a styling tool , it also offers some out-of-the-box utils + a ready-made design system that you can use right away.
It's not a popular solution, the approach is similar to React Native StyleSheets way of styling components. Has built-in TypeScript support and a simple API.
I got it started with Next.js, but it feels fragile. The Glamor official example throws an error regarding rehydrate . When commenting it out, it works, but not sure what the consequences are.
Didn't manage to start it with Next.js + TypeScript. The official example uses version 3, while today we have version 6. The example doesn't work, because the API has changed.
The solution looked interesting, because it is supposed to be very light-weight.
Didn't manage to start it with Next.js + TypeScript. There was an official example that used an older version of Next.js, but the example if not there anymore.
The solution is not that popular, but it was the first to use .css extraction with collocated styles.
Looks promising, atomic css and light-weight. It has a working Next.js example, but we didn't consider it because it lacks any documentation.
It looks like a not so popular solution, which also lacks support for TypeScript. It looks like the maintainers work at Uber and they use it internally. It focused on generating unique atomic CSS classes, which could potentially deduplicate a lot of code.
The project was put in Maintenance Mode. They recommend other solutions.
The project was discontinued in favor of Emotion.
Each implementation sits on their own branch, so we can have a clear separation at built time.
# install dependencies
yarn
# for development
yarn dev
# for production
yarn build
yarn startTo get in touch, my DMs are open @pfeiffer_andrei.
Special thanks and appreciations go to everyone that helped putting this document together, and making it more accurate: