Kurs JavaScript i React · Moduł 14: Zaawansowane stylowanie

Design Tokens i Systematyczny CSS - Kosmiczny Kod Designu

5 min czytania
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. 1. Czym są design tokens w kontekście frontendu?

  2. 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

Przydatne artykuły