CSS-in-JS Solution for Components
CSS-in-JS is an approach where styles are defined directly in JavaScript/TypeScript component code. Styles are isolated, typed, can use props and variables without CSS variables. Trade-off: either runtime overhead (styled-components, Emotion) or build setup requirement (vanilla-extract, Linaria).
Library choice
| Library | Runtime | SSR | Bundle | When to choose |
|---|---|---|---|---|
| Emotion | Yes | Yes | ~8KB | React projects, need dynamics |
| styled-components | Yes | Yes | ~13KB | Classic, large ecosystem |
| vanilla-extract | No | Yes | 0 | Static styles, max performance |
| Linaria | No | Yes | 0 | Babel/Vite, static with interpolation |
| Panda CSS | No | Yes | 0 | Atomic, design tokens, modern |
For new React projects — vanilla-extract (zero-runtime) or Emotion (if need dynamic styles via props).
Emotion: basic setup
npm install @emotion/react @emotion/styled
npm install -D @emotion/babel-plugin # for optimization
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['@emotion/babel-plugin'],
},
}),
],
})
Styled components with Emotion
import styled from '@emotion/styled'
import { css } from '@emotion/react'
const Button = styled.button<{
variant?: 'primary' | 'secondary'
size?: 'sm' | 'md' | 'lg'
}>`
display: inline-flex;
align-items: center;
gap: 8px;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: background-color 150ms ease;
${({ size = 'md' }) =>
({
sm: css`padding: 6px 12px; font-size: 13px;`,
md: css`padding: 10px 20px; font-size: 14px;`,
lg: css`padding: 14px 28px; font-size: 16px;`,
}[size])}
${({ variant = 'primary' }) =>
({
primary: css`
background: #2563eb;
color: white;
&:hover { background: #1d4ed8; }
`,
secondary: css`
background: transparent;
color: #2563eb;
border: 2px solid #2563eb;
`,
}[variant])}
`
css prop
import { css } from '@emotion/react'
function Card({ featured }: { featured?: boolean }) {
return (
<div
css={css`
padding: 16px;
border-radius: 8px;
background: ${featured ? '#eff6ff' : '#ffffff'};
border: 1px solid ${featured ? '#3b82f6' : '#e2e8f0'};
`}
>
Card content
</div>
)
}
Zero-runtime with vanilla-extract
// button.css.ts
import { style } from '@vanilla-extract/css'
export const button = style({
display: 'inline-flex',
alignItems: 'center',
gap: 8,
borderRadius: 8,
padding: '10px 20px',
fontWeight: 500,
cursor: 'pointer',
background: '#2563eb',
color: 'white',
selectors: {
'&:hover': { background: '#1d4ed8' },
'&:disabled': { opacity: 0.5, cursor: 'not-allowed' },
},
})







