JavaScript and React course Β· Module 14: Advanced Styling
CSS-in-JS - Advanced Navigation Systems
In this lesson5
Styled-components is not the only CSS-in-JS library. There are others, each with unique advantages. Let's explore two of the most important ones: Emotion and vanilla-extract.
Emotion
Emotion is a CSS-in-JS library that offers two ways of using it:
Object API (@emotion/react)
1/** @jsxImportSource @emotion/react */
2import { css } from '@emotion/react';
3
4function SpacePanel({ isActive }) {
5 return (
6 <div css={css`
7 background: #0d1137;
8 border: 2px solid ${isActive ? '#00e676' : '#3949ab'};
9 border-radius: 12px;
10 padding: 20px;
11 transition: border-color 0.3s ease;
12 `}>
13 <h2>Navigation Panel</h2>
14 </div>
15 );
16}Styled API (@emotion/styled)
1import styled from '@emotion/styled';
2
3const Card = styled.div`
4 background: #1a1a4e;
5 padding: 20px;
6 border-radius: 12px;
7`;
8
9// Or object syntax:
10const CardObj = styled.div({
11 background: '#1a1a4e',
12 padding: 20,
13 borderRadius: 12,
14});Advantages of Emotion
- Two APIs -- you can use template literals or JS objects
- Lightweight -- smaller bundle than styled-components
- High performance -- optimized runtime
- css prop -- you can add styles directly via the
cssprop
Vanilla-extract -- Zero-runtime CSS-in-JS
Vanilla-extract is a completely different approach: styles are generated at compile time, not at runtime. Zero JavaScript in the client bundle!
1// styles.css.ts
2import { style, globalStyle } from '@vanilla-extract/css';
3
4export const panel = style({
5 background: '#0d1137',
6 border: '2px solid #3949ab',
7 borderRadius: 12,
8 padding: 20,
9 ':hover': {
10 borderColor: '#7c4dff',
11 },
12});
13
14export const title = style({
15 color: '#7c4dff',
16 fontSize: 24,
17});1// Component.tsx
2import { panel, title } from './styles.css.ts';
3
4function SpacePanel() {
5 return (
6 <div className={panel}>
7 <h2 className={title}>Panel</h2>
8 </div>
9 );
10}Advantages of vanilla-extract
- Zero runtime -- no additional JS in the browser
- TypeScript-first -- full typing, auto-completion
- Type-safe -- compilation error when using a non-existent style
- Theming -- built-in theme system with CSS variables
CSS-in-JS Comparison
| Feature | Styled-components | Emotion | Vanilla-extract |
|---|---|---|---|
| Runtime | Yes | Yes | No (zero-runtime) |
| Bundle | Larger | Medium | Minimal |
| TypeScript | Good support | Good support | Native TS |
| SSR | Requires configuration | Requires configuration | Seamless |
| DX | Very good | Very good | Requires build |
When to use which library?
The choice of CSS-in-JS library depends on several factors:
Choose styled-components when:
- Your team already knows this library -- it's the most popular and has the largest community
- You need a rich ecosystem of plugins and tools
- You care about code readability -- tagged template literals are intuitive for developers who know CSS
Choose Emotion when:
- You want flexibility -- you can use both template literals and JS objects
- You care about a smaller bundle size
- You use Next.js or another framework where Emotion has better integration
- You need the
cssprop for quickly adding inline-like styles with the full power of CSS
Choose vanilla-extract when:
- Performance is the number one priority -- zero JS in the client bundle
- You use TypeScript and want full type safety
- You're building a large application where bundle size matters
- You don't need dynamic styles based on runtime props
Trend: zero-runtime CSS-in-JS
More and more projects are moving to zero-runtime solutions like vanilla-extract, Panda CSS, or Linaria. The reason? In large applications, runtime CSS-in-JS (styled-components, Emotion) can slow down rendering because it generates styles in JavaScript while the application is running.
Zero-runtime generates CSS classes at compile time -- it's like preparing a navigation map before launch instead of calculating the route in flight. The result: faster page loading and better Core Web Vitals scores.
1// Panda CSS -- another popular zero-runtime alternative
2import { css } from '../styled-system/css';
3
4function Panel() {
5 return (
6 <div className={css({
7 bg: 'slate.900',
8 p: '4',
9 rounded: 'xl',
10 border: '2px solid',
11 borderColor: 'indigo.700'
12 })}>
13 Panel content
14 </div>
15 );
16}Regardless of which library you choose, the key is to stick with one approach within a project and establish conventions with your team.
Code for this lesson: App.jsx
1import React from 'react';
2import './styles.css';
3
4// Showcase of different CSS-in-JS: Emotion, vanilla-extract
5// (In Sandpack we can't use Emotion/vanilla-extract,
6// so we demonstrate the syntax as a comparison)
7
8function CodeBlock({ title, code, language }) {
9 return (
10 <div className="code-block">
11 <div className="code-header">
12 <span className="code-lang">{language}</span>
13 <span className="code-title">{title}</span>
14 </div>
15 <pre className="code-content">{code}</pre>
16 </div>
17 );
18}
19
20function ComparisonSection({ title, children }) {
21 return (
22 <div className="section">
23 <h2 className="section-title">{title}</h2>
24 {children}
25 </div>
26 );
27}
28
29export default function App() {
30 return (
31 <div className="app">
32 <h1 className="heading">CSS-in-JS: Emotion vs Vanilla-extract</h1>
33 <p className="subtitle">Advanced styling libraries</p>
34
35 <ComparisonSection title="Emotion -- css prop">
36 <CodeBlock
37 title="Inline styles z css prop"
38 language="JSX"
39 code={`/** @jsxImportSource @emotion/react */
40import { css } from '@emotion/react';
41
42function Panel({ isActive }) {
43 return (
44 <div css={css\`
45 background: #0d1137;
46 border: 2px solid \${isActive ? '#00e676' : '#3949ab'};
47 padding: 20px;
48 \`}>
49 Panel content
50 </div>
51 );
52}`}
53 />
54 <div className="feature-list">
55 <div className="feature">
56 <span className="feature-icon good">+</span>
57 <span>The css prop -- styles directly on the element</span>
58 </div>
59 <div className="feature">
60 <span className="feature-icon good">+</span>
61 <span>Smaller bundle than styled-components</span>
62 </div>
63 <div className="feature">
64 <span className="feature-icon good">+</span>
65 <span>Supports both object syntax and template literals</span>
66 </div>
67 </div>
68 </ComparisonSection>
69
70 <ComparisonSection title="Vanilla-extract -- zero runtime">
71 <CodeBlock
72 title="Styles generated at compile time"
73 language="TypeScript"
74 code={`// styles.css.ts
75import { style } from '@vanilla-extract/css';
76
77export const panel = style({
78 background: '#0d1137',
79 border: '2px solid #3949ab',
80 padding: 20,
81 ':hover': {
82 borderColor: '#7c4dff',
83 },
84});
85
86// Component.tsx
87import { panel } from './styles.css.ts';
88<div className={panel}>Panel</div>`}
89 />
90 <div className="feature-list">
91 <div className="feature">
92 <span className="feature-icon good">+</span>
93 <span>Zero runtime -- no JS for styles in the bundle</span>
94 </div>
95 <div className="feature">
96 <span className="feature-icon good">+</span>
97 <span>TypeScript-first -- full typing</span>
98 </div>
99 <div className="feature">
100 <span className="feature-icon warn">!</span>
101 <span>Requires build configuration</span>
102 </div>
103 </div>
104 </ComparisonSection>
105
106 <div className="comparison-table">
107 <h2 className="section-title">Comparison</h2>
108 <table>
109 <thead>
110 <tr>
111 <th>Feature</th>
112 <th>Styled-comp.</th>
113 <th>Emotion</th>
114 <th>Vanilla-extract</th>
115 </tr>
116 </thead>
117 <tbody>
118 <tr><td>Runtime JS</td><td>Yes</td><td>Yes</td><td className="highlight">No</td></tr>
119 <tr><td>Bundle size</td><td>Large</td><td>Medium</td><td className="highlight">Minimal</td></tr>
120 <tr><td>TypeScript</td><td>Good</td><td>Good</td><td className="highlight">Native</td></tr>
121 <tr><td>SSR</td><td>Configuration</td><td>Configuration</td><td className="highlight">Seamless</td></tr>
122 <tr><td>DX</td><td className="highlight">Great</td><td className="highlight">Great</td><td>Good</td></tr>
123 </tbody>
124 </table>
125 </div>
126 </div>
127 );
128}Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Which CSS-in-JS library generates styles at compile time (zero runtime)?
2. How does Emotion with css prop differ from styled-components?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Vertical ordering
Arrange the stages of style processing in vanilla-extract from start to finish:
- Horizontal ordering
Arrange the elements needed to use the css prop in Emotion:
- Code editor
Implement an expandable accordion with conditional clsx
- Click in order
Arrange the style definition syntax in vanilla-extract: