Pilotze, wyobraz sobie, ze kazdy statek w flocie galaktycznej uzywa dokladnie tych samych kolorow statusu, tych samych odstepow miedzy panelami i tej samej typografii na monitorach. To nie przypadek -- to design system. W swiecie frontendu odpowiednikiem sa design tokens -- centralne zrodlo prawdy dla wszystkich wartosci wizualnych w aplikacji.
Design tokens to nazwane wartosci reprezentujace decyzje designowe: kolory, odstepy, rozmiary czcionek, cienie, promienie zaokraglen. Zamiast rozrzucac magiczne liczby po kodzie, definiujesz je w jednym miejscu:
1// tokens.js -- centralne zrodlo prawdy
2const tokens = {
3 colors: {
4 primary: '#7c4dff',
5 primaryLight: '#b388ff',
6 primaryDark: '#651fff',
7 success: '#00e676',
8 warning: '#ffa726',
9 danger: '#f44336',
10 bgDark: '#0a0a2e',
11 bgSurface: '#1a1a4e',
12 textPrimary: '#e0e0e0',
13 textMuted: '#9e9e9e',
14 border: '#3949ab',
15 },
16 spacing: {
17 xs: '4px',
18 sm: '8px',
19 md: '16px',
20 lg: '24px',
21 xl: '32px',
22 xxl: '48px',
23 },
24 fontSize: {
25 xs: '12px',
26 sm: '14px',
27 md: '16px',
28 lg: '20px',
29 xl: '28px',
30 xxl: '36px',
31 },
32 borderRadius: {
33 sm: '4px',
34 md: '8px',
35 lg: '12px',
36 xl: '20px',
37 full: '9999px',
38 },
39 shadows: {
40 sm: '0 2px 4px rgba(0,0,0,0.1)',
41 md: '0 4px 12px rgba(0,0,0,0.2)',
42 lg: '0 8px 24px rgba(0,0,0,0.3)',
43 glow: '0 0 20px rgba(124,77,255,0.3)',
44 },
45};Gdy masz zdefiniowane tokeny, uzywasz ich zamiast bezposrednich wartosci:
1import { tokens } from './tokens';
2
3// W inline styles
4function Card({ children }) {
5 return (
6 <div style={{
7 background: tokens.colors.bgSurface,
8 padding: tokens.spacing.lg,
9 borderRadius: tokens.borderRadius.lg,
10 border: `1px solid ${tokens.colors.border}`,
11 }}>
12 {children}
13 </div>
14 );
15}1// W styled-components
2const Card = styled.div`
3 background: ${tokens.colors.bgSurface};
4 padding: ${tokens.spacing.lg};
5 border-radius: ${tokens.borderRadius.lg};
6 border: 1px solid ${tokens.colors.border};
7`;Najlepszy sposob to polaczenie tokenow JavaScript z CSS Variables -- definiujesz tokeny w JS (latwe do zarzadzania), a przekazujesz do CSS jako zmienne (wydajne renderowanie):
1function applyTokens(tokens) {
2 const vars = {};
3 vars['--color-primary'] = tokens.colors.primary;
4 vars['--color-bg'] = tokens.colors.bgDark;
5 vars['--color-surface'] = tokens.colors.bgSurface;
6 vars['--color-text'] = tokens.colors.textPrimary;
7 vars['--spacing-md'] = tokens.spacing.md;
8 vars['--spacing-lg'] = tokens.spacing.lg;
9 vars['--radius-lg'] = tokens.borderRadius.lg;
10 return vars;
11}
12
13function App() {
14 return (
15 <div style={applyTokens(tokens)} className="app">
16 {/* CSS uzywa var(--color-primary) itd. */}
17 </div>
18 );
19}Dobry system kolorow ma strukture:
1const colorSystem = {
2 // Kolory bazowe
3 blue: { 50: '#e3f2fd', 100: '#bbdefb', 500: '#2196f3', 900: '#0d47a1' },
4 purple: { 50: '#f3e5f5', 100: '#e1bee7', 500: '#9c27b0', 900: '#4a148c' },
5
6 // Kolory semantyczne (uzywane w kodzie)
7 primary: 'var(--blue-500)',
8 success: 'var(--green-500)',
9 warning: 'var(--orange-500)',
10 danger: 'var(--red-500)',
11
12 // Kolory powierzchni
13 background: '#0a0a2e',
14 surface: '#1a1a4e',
15 surfaceHover: '#2a2a5e',
16};Kolory semantyczne (primary, success, danger) oddzielasz od kolorow bazowych (blue-500, red-400). Dzieki temu zmiana motywu to zmiana mapowania, nie szukanie po calym kodzie.
Spacing (odstepy) oparty na skali -- kazda nastepna wartosc jest wielokrotnoscia bazowej:
1// Skala 4px (Tailwind-style)
2const spacing = {
3 0: '0px',
4 1: '4px',
5 2: '8px',
6 3: '12px',
7 4: '16px',
8 5: '20px',
9 6: '24px',
10 8: '32px',
11 10: '40px',
12 12: '48px',
13 16: '64px',
14};Uzywanie skali zamiast dowolnych wartosci gwarantuje wizualna spojnosc -- elementy na ekranie sa rownomiernie rozmieszczone, co daje profesjonalny wyglad.
1const typography = {
2 fontFamily: {
3 sans: "'Inter', 'Segoe UI', sans-serif",
4 mono: "'Fira Code', monospace",
5 },
6 fontSize: {
7 xs: ['12px', { lineHeight: '16px' }],
8 sm: ['14px', { lineHeight: '20px' }],
9 base: ['16px', { lineHeight: '24px' }],
10 lg: ['20px', { lineHeight: '28px' }],
11 xl: ['28px', { lineHeight: '36px' }],
12 '2xl': ['36px', { lineHeight: '44px' }],
13 },
14 fontWeight: {
15 normal: 400,
16 medium: 500,
17 semibold: 600,
18 bold: 700,
19 },
20};Kazdy rozmiar czcionki ma przyporzadkowana wysokosc linii -- to eliminuje chaos typograficzny.
Zaawansowany wzorzec to skladanie tokenow -- tworzenie bardziej szczegolowych tokenow z bazowych:
1// Tokeny bazowe (primitive)
2const primitives = {
3 blue500: '#7c4dff',
4 blue300: '#b388ff',
5 green500: '#00e676',
6 red500: '#f44336',
7 gray900: '#0a0a2e',
8 gray800: '#1a1a4e',
9 gray100: '#e0e0e0',
10 gray400: '#9e9e9e',
11};
12
13// Tokeny semantyczne (zlozone z bazowych)
14const semantic = {
15 colorPrimary: primitives.blue500,
16 colorPrimaryLight: primitives.blue300,
17 colorSuccess: primitives.green500,
18 colorDanger: primitives.red500,
19 colorBackground: primitives.gray900,
20 colorSurface: primitives.gray800,
21 colorText: primitives.gray100,
22 colorTextMuted: primitives.gray400,
23};
24
25// Tokeny komponentowe (zlozone z semantycznych)
26const components = {
27 buttonPrimaryBg: semantic.colorPrimary,
28 buttonPrimaryText: '#ffffff',
29 buttonDangerBg: semantic.colorDanger,
30 cardBg: semantic.colorSurface,
31 cardBorder: primitives.blue500 + '40',
32};Trzy warstwy: bazowe (wartosci) -> semantyczne (znaczenie) -> komponentowe (uzycie). Zmiana jednego bazowego tokenu kaskadowo aktualizuje wszystko.
W duzych projektach tokeny przechowuje sie w formacie JSON i automatycznie generuje pliki dla roznych platform za pomoca narzedzia Style Dictionary:
1{
2 "color": {
3 "primary": { "value": "#7c4dff" },
4 "success": { "value": "#00e676" },
5 "background": { "value": "#0a0a2e" }
6 },
7 "spacing": {
8 "sm": { "value": "8px" },
9 "md": { "value": "16px" },
10 "lg": { "value": "24px" }
11 }
12}Style Dictionary generuje z tego:
Jedno zrodlo prawdy, wiele formatow wyjsciowych.
Tworzenie komponentu
TokenPreview pomaga kontrolowac caly system:1function TokenPreview({ tokens }) {
2 return (
3 <div>
4 <h2>Kolory</h2>
5 {Object.entries(tokens.colors).map(([name, value]) => (
6 <div key={name} style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
7 <div style={{ width: 40, height: 40, background: value, borderRadius: 8 }} />
8 <span>{name}: {value}</span>
9 </div>
10 ))}
11
12 <h2>Spacing</h2>
13 {Object.entries(tokens.spacing).map(([name, value]) => (
14 <div key={name} style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
15 <div style={{ width: value, height: 16, background: '#7c4dff' }} />
16 <span>{name}: {value}</span>
17 </div>
18 ))}
19 </div>
20 );
21}