Kurs JavaScript i React · Moduł 14: Zaawansowane stylowanie
Design Tokens i Systematyczny CSS - Kosmiczny Kod Designu
W tej lekcji10
Pilotze, wyobraź sobie, że każdy statek w flocie galaktycznej używa dokładnie tych samych kolorów statusu, tych samych odstępów między panelami i tej samej typografii na monitorach. To nie przypadek -- to design system. W świecie frontendu odpowiednikiem są design tokens -- centralne źródło prawdy dla wszystkich wartości wizualnych w aplikacji.
Czym są Design Tokens?
Design tokens to nazwane wartości reprezentujące decyzje designowe: kolory, odstępy, rozmiary czcionek, cienie, promienie zaokrągleń. Zamiast rozrzucać magiczne liczby po kodzie, definiujesz je w jednym miejscu:
1// tokens.js -- centralne źródło 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};Używanie tokenów w komponentach
Gdy masz zdefiniowane tokeny, używasz ich zamiast bezpośrednich wartości:
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`;Tokeny jako CSS Variables
Najlepszy sposób to połączenie tokenów JavaScript z CSS Variables -- definiujesz tokeny w JS (łatwe do zarządzania), 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 używa var(--color-primary) itd. */}
17 </div>
18 );
19}Spójny system kolorów
Dobry system kolorów ma strukturę:
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 (używane 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 kolorów bazowych (blue-500, red-400). Dzięki temu zmiana motywu to zmiana mapowania, nie szukanie po całym kodzie.
System spacingu
Spacing (odstępy) oparty na skali -- każda następna wartość jest wielokrotnością 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};Używanie skali zamiast dowolnych wartości gwarantuje wizualną spójność -- elementy na ekranie są równomiernie rozmieszczone, co daje profesjonalny wygląd.
Typografia jako system
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};Każdy rozmiar czcionki ma przyporządkowaną wysokość linii -- to eliminuje chaos typograficzny.
Token Composition -- składanie tokenów
Zaawansowany wzorzec to składanie tokenów -- tworzenie bardziej szczegółowych tokenów 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 (złożone 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 (złożone 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 (wartości) -> semantyczne (znaczenie) -> komponentowe (użycie). Zmiana jednego bazowego tokenu kaskadowo aktualizuje wszystko.
Style Dictionary -- automatyzacja tokenów
W dużych projektach tokeny przechowuje się w formacie JSON i automatycznie generuje pliki dla różnych platform za pomocą narzędzia 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:
- CSS Variables -- dla aplikacji webowych
- JavaScript/TypeScript -- dla React
- Swift/Kotlin -- dla aplikacji mobilnych
Jedno źródło prawdy, wiele formatów wyjściowych.
Automatyzacja spójności
Tworzenie komponentu TokenPreview pomaga kontrolować cały 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}Kiedy używać design tokens?
- Każdy projekt większy niż landing page -- tokeny oszczędzają czas w dłuższej perspektywie
- Praca zespołowa -- każdy developer używa tych samych wartości
- Wiele motywów -- zmiana jednego obiektu zmienia cały wygląd
- Spójność między platformami -- Style Dictionary generuje tokeny dla React, React Native, iOS, Android
- Składanie tokenów -- trzy warstwy (bazowe -> semantyczne -> komponentowe) dają pełną kontrolę
Kod do tej lekcji: App.jsx
1import React, { useState } from 'react';
2import './styles.css';
3
4// Design tokens -- centralne zrodlo prawdy
5const tokens = {
6 colors: {
7 primary: '#7c4dff', primaryLight: '#b388ff', primaryDark: '#651fff',
8 success: '#00e676', warning: '#ffa726', danger: '#f44336',
9 bgDark: '#0a0a2e', bgSurface: '#1a1a4e',
10 textPrimary: '#e0e0e0', textMuted: '#9e9e9e', border: '#3949ab',
11 },
12 spacing: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px' },
13 fontSize: { xs: '12px', sm: '14px', md: '16px', lg: '20px', xl: '28px' },
14 borderRadius: { sm: '4px', md: '8px', lg: '12px', full: '9999px' },
15};
16
17// Konwersja tokenow na CSS Variables
18function tokensToVars(t) {
19 return {
20 '--color-primary': t.colors.primary,
21 '--color-primary-light': t.colors.primaryLight,
22 '--color-success': t.colors.success,
23 '--color-warning': t.colors.warning,
24 '--color-danger': t.colors.danger,
25 '--color-bg': t.colors.bgDark,
26 '--color-surface': t.colors.bgSurface,
27 '--color-text': t.colors.textPrimary,
28 '--color-text-muted': t.colors.textMuted,
29 '--color-border': t.colors.border,
30 '--spacing-sm': t.spacing.sm,
31 '--spacing-md': t.spacing.md,
32 '--spacing-lg': t.spacing.lg,
33 '--radius-lg': t.borderRadius.lg,
34 };
35}
36
37// Podglad kolorow
38function ColorSwatch({ name, value }) {
39 return (
40 <div className="swatch">
41 <div className="swatch-color" style={{ background: value }} />
42 <div className="swatch-info">
43 <span className="swatch-name">{name}</span>
44 <span className="swatch-value">{value}</span>
45 </div>
46 </div>
47 );
48}
49
50// Podglad spacing
51function SpacingBar({ name, value }) {
52 return (
53 <div className="spacing-row">
54 <span className="spacing-name">{name}</span>
55 <div className="spacing-bar" style={{ width: value, background: 'var(--color-primary)' }} />
56 <span className="spacing-value">{value}</span>
57 </div>
58 );
59}
60
61// Przyklad uzycia tokenow w komponencie
62function TokenCard({ title, children }) {
63 return (
64 <div className="token-card">
65 <h3 className="token-card-title">{title}</h3>
66 {children}
67 </div>
68 );
69}
70
71function TokenButton({ variant, children }) {
72 return (
73 <button className={`token-btn token-btn-${variant}`}>{children}</button>
74 );
75}
76
77export default function App() {
78 return (
79 <div style={tokensToVars(tokens)} className="app">
80 <h1 className="heading">Design Tokens</h1>
81 <p className="subtitle">Centralne zrodlo prawdy -- wszystkie wartosci w jednym miejscu</p>
82
83 <TokenCard title="Kolory">
84 <div className="swatch-grid">
85 {Object.entries(tokens.colors).map(([name, value]) => (
86 <ColorSwatch key={name} name={name} value={value} />
87 ))}
88 </div>
89 </TokenCard>
90
91 <TokenCard title="Spacing">
92 {Object.entries(tokens.spacing).map(([name, value]) => (
93 <SpacingBar key={name} name={name} value={value} />
94 ))}
95 </TokenCard>
96
97 <TokenCard title="Komponenty uzywajace tokenow">
98 <div className="component-demo">
99 <TokenButton variant="primary">Primary</TokenButton>
100 <TokenButton variant="success">Success</TokenButton>
101 <TokenButton variant="danger">Danger</TokenButton>
102 <TokenButton variant="outline">Outline</TokenButton>
103 </div>
104 <p className="demo-note">
105 Kazdy przycisk uzywa var(--color-...) z tokenow.
106 Zmiana tokenu zmienia wszystkie komponenty naraz!
107 </p>
108 </TokenCard>
109 </div>
110 );
111}Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Czym są design tokens w kontekście frontendu?
2. Dlaczego w systemie design tokens warto oddzielać kolory semantyczne (primary, danger) od bazowych (blue-500, red-400)?
To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w poziomie
Ułóż składnię użycia tokenu koloru w CSS z CSS Variable:
- Układanie w poziomie
Ułóż składnię definicji tokenu spacing w obiekcie JavaScript:
- Klikanie w kolejności
Ułóż kroki konwersji tokenu JavaScript na CSS Variable w React:
- Układanie w pionie
Ułóż warstwy systemu design tokens od najbardziej podstawowej do najbardziej konkretnej:
- Układanie w pionie
Ułóż kroki wdrażania design tokens w nowym projekcie React:
- Edytor kodu
Stwórz system design tokens i użyj go w komponencie
- Układanie w pionie
Ułóż tokeny rozmiarów czcionek od najmniejszego do największego:
- Edytor kodu
Stwórz wizualny podgląd tokenów kolorowych