CSS-in-JS рішення для компонентів
CSS-in-JS — підхід, при якому стилі визначаються прямо в JavaScript/TypeScript коді компонента. Стилі ізольовані, типізовані, можуть використовувати props та змінні без CSS-змінних. Компромісс: або runtime-витрати (styled-components, Emotion), або необхідність налаштування збірки (vanilla-extract, Linaria).
Вибір бібліотеки
| Бібліотека | Runtime | SSR | Bundle | Коли вибирати |
|---|---|---|---|---|
| Emotion | Так | Так | ~8KB | React-проекти, потрібна динаміка |
| styled-components | Так | Так | ~13KB | Класика, велика екосистема |
| vanilla-extract | Ні | Так | 0 | Статичні стилі, максимум продуктивності |
| Linaria | Ні | Так | 0 | Babel/Vite, статика з інтерполяцією |
| Panda CSS | Ні | Так | 0 | Atomic, design tokens, сучасний |
Для нового React-проекту — vanilla-extract (zero-runtime) або Emotion (якщо потрібні динамічні стилі через props).
Emotion: базова налаштування
npm install @emotion/react @emotion/styled
npm install -D @emotion/babel-plugin # для оптимізації
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['@emotion/babel-plugin'],
},
}),
],
})
Styled компоненти з 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 з 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' },
},
})







