Design system to wspólny zestaw tokenów (kolorów, odstępów, typografii), gotowych komponentów, wzorców użycia i dokumentacji, który pozwala zespołowi budować spójny interfejs bez wymyślania wszystkiego od nowa. To nie jest sama biblioteka komponentów ani plik z Figmy, tylko źródło prawdy dla designerów i programistów. W praktyce zaczyna się od tokenów w CSS i kilku komponentach, a rośnie wraz z projektem.
Design system pojawia się w ofertach pracy częściej, niż się wydaje, a junior często pierwszy raz styka się z nim dopiero w pierwszym zespole. Warto więc wiedzieć, czym jest, z czego się składa i jak wygląda w kodzie, zanim dostaniesz zadanie „dodaj ten przycisk zgodnie z design systemem”.
Czym jest design system i czym różni się od biblioteki komponentów?
Design system to zbiór zasad, elementów i narzędzi, który opisuje, jak wygląda i jak działa interfejs produktu. Łączy w jednym miejscu to, co zwykle jest rozproszone: decyzje designerów, kod komponentów i dokumentację. Dzięki temu każdy ekran w aplikacji korzysta z tych samych kolorów, odstępów i zachowań, a zmiana w jednym miejscu rozchodzi się na cały produkt.
Najczęściej mylone są trzy pojęcia:
| Pojęcie | Co zawiera | Czego brakuje |
|---|---|---|
| Style guide | Opis kolorów, fontów i tonu komunikacji, zwykle jako dokument lub strona | Gotowego kodu i zasad użycia komponentów |
| Biblioteka komponentów (component library) | Gotowe komponenty w kodzie, np. Button, Input, Modal | Decyzji, kiedy i jak ich używać, oraz wspólnych tokenów z designem |
| Design system | Tokeny, komponenty, wzorce użycia, dokumentacja i zasady współpracy | Nic z powyższego nie jest „opcjonalne”, ale całość można budować stopniowo |
Biblioteka komponentów jest więc częścią design systemu, ale nie jest nim samym. Możesz mieć świetnie napisany Button i nadal nie mieć design systemu, jeśli nikt nie ustalił, kiedy używać wariantu głównego, a kiedy drugorzędnego.
Z czego składa się design system?
Cztery warstwy pojawiają się w niemal każdym design systemie:
- Tokeny (design tokens) — nazwane wartości, np. kolor główny, podstawowy odstęp czy promień zaokrąglenia. To najniższa warstwa i „słownik” całego systemu.
- Komponenty — gotowe elementy interfejsu zbudowane z tokenów: przyciski, pola formularzy, karty, modale.
- Wzorce — sposoby łączenia komponentów w całe rozwiązania, np. formularz logowania, tabela z filtrami albo komunikat o błędzie.
- Dokumentacja — opis tego, jak i kiedy używać każdego elementu, wraz z przykładami. Bez niej system szybko zamienia się w folder z plikami, których nikt nie rozumie.
Dzięki takiemu podziałowi design system działa jak jedno źródło prawdy (single source of truth): designer i programista patrzą na te same nazwy i te same wartości, a nie na własne wersje tego samego koloru.
Jak zacząć budować design system w projekcie frontendowym?
Nie zaczynaj od wielkiej biblioteki. Najrozsądniej wystartować od tokenów jako zmiennych CSS, a dopiero potem budować na nich komponenty.
Jak zapisać tokeny jako zmienne CSS?
Zmienne CSS (custom properties) to najprostszy sposób na zapisanie tokenów, bo działają bez żadnych dodatkowych narzędzi i można je nadpisywać w dowolnym miejscu drzewa DOM:
:root {
--color-primary: #3b5bdb;
--color-primary-hover: #364fc7;
--color-text: #212529;
--space-2: 0.5rem;
--space-4: 1rem;
--radius-md: 0.5rem;
--font-size-md: 1rem;
}
Zwróć uwagę na nazwy: --color-primary mówi, do czego służy kolor, a nie jaki jest (--blue). Dzięki temu, gdy marka zmieni barwę główną, edytujesz jedną wartość, a nie szukasz niebieskiego po całym projekcie. Podobną dyscyplinę nazewnictwa znasz z metodologii BEM w CSS: w obu przypadkach chodzi o przewidywalne nazwy, które mówią, co element robi.
Jak napisać pierwszy komponent oparty na tokenach?
Przykład napisałem w React, ale ta sama idea działa w każdym frameworku. Komponent powinien korzystać z tokenów, a nie z „magicznych” wartości wpisanych na sztywno:
import styles from "./Button.module.css";
export function Button({ variant = "primary", children, ...props }) {
return (
<button className={`${styles.button} ${styles[variant]}`} {...props}>
{children}
</button>
);
}
.button {
padding: var(--space-2) var(--space-4);
border: 0;
border-radius: var(--radius-md);
font-size: var(--font-size-md);
cursor: pointer;
}
.primary {
background: var(--color-primary);
color: #fff;
}
.primary:hover {
background: var(--color-primary-hover);
}
Wariant (primary, secondary) jest jedynym „publicznym” pokrętłem komponentu. Reszta wyglądu wynika z tokenów, więc zmiana motywu nie wymaga dotykania samego komponentu. Jeśli chcesz sprawdzić, czy taki komponent zachowuje się poprawnie, zobacz testowanie komponentów w React z Testing Library.
Jak dokumentować komponenty design systemu?
Komponent bez dokumentacji nie jest częścią systemu, tylko kolejnym plikiem w repozytorium. Dokumentacja odpowiada na trzy pytania: jak wygląda ten element, jakie ma warianty i kiedy go użyć (a kiedy nie).
Najpopularniejszym narzędziem do tego w świecie Reacta jest Storybook. Pozwala opisać każdy komponent w osobnych „historiach” (stories), przeglądać je w przeglądarce i ręcznie zmieniać propsy bez uruchamiania całej aplikacji. Dla zespołu to także wspólny punkt odniesienia z designerami: zamiast rozmawiać o zrzutach ekranu, oglądacie działający komponent. Szczegóły konfiguracji to temat na osobny poradnik — tutaj ważne jest, że dokumentacja to nie dodatek, tylko równorzędna część systemu.
Jak wygląda dokumentacja kilku komponentów w Storybooku?
Każdy komponent dostaje obok siebie plik *.stories.jsx, który opisuje jego warianty jako osobne „historie". Zacznijmy od Button z poprzedniego przykładu:
import { Button } from "./Button";
export default {
title: "Komponenty/Button",
component: Button,
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["primary", "secondary"],
description: "Wariant wizualny przycisku",
},
},
};
export const Primary = {
args: { variant: "primary", children: "Zapisz" },
};
export const Secondary = {
args: { variant: "secondary", children: "Anuluj" },
};
export const Disabled = {
args: { variant: "primary", children: "Zapisz", disabled: true },
};
Każdy wyeksportowany obiekt (Primary, Secondary, Disabled) to jedna historia, czyli gotowy stan komponentu widoczny w przeglądarce. Dzięki tags: ["autodocs"] Storybook sam zbuduje stronę dokumentacji z opisem propsów, a argTypes pozwala zmieniać variant z poziomu interfejsu, bez dotykania kodu.
Ten sam wzorzec powtarzasz dla kolejnych komponentów. Dla pola formularza (Input):
import { Input } from "./Input";
export default {
title: "Komponenty/Input",
component: Input,
tags: ["autodocs"],
};
export const Default = {
args: { label: "Adres e-mail", placeholder: "jan@example.com" },
};
export const WithError = {
args: { label: "Adres e-mail", error: "Podaj poprawny adres" },
};
Stany takie jak WithError są najcenniejsze, bo pokazują przypadki, o których łatwo zapomnieć (błąd, wyłączony, ładowanie). Designer widzi je wszystkie na jednej stronie i może zgłosić uwagi, zanim komponent trafi do aplikacji.
Jak pozwolić na nadpisywanie stylów komponentu bez rozwalania spójności?
To jedno z najczęstszych praktycznych pytań: skoro komponent ma wyglądać zawsze tak samo, to co zrobić, gdy w jednym miejscu potrzebujesz niewielkiej zmiany? Źle jest nadpisywać style zewnętrznym selektorem albo !important, bo wtedy system przestaje być spójny i każde nadpisanie żyje własnym życiem.
Bezpieczniejsze podejścia, od najlepszego:
| Sposób | Kiedy go użyć | Ryzyko |
|---|---|---|
| Nowy wariant w komponencie | Zmiana powtarza się w kilku miejscach | Wymaga uzgodnienia z designem |
| Nadpisanie tokena w ograniczonym zakresie (np. na kontenerze) | Cała sekcja ma inny motyw | Trzeba pilnować, które tokeny wolno nadpisywać |
Prop className doklejany do komponentu | Jednorazowa korekta układu, np. margines | Łatwo nadużyć i rozjechać wygląd |
!important albo selektor z zewnątrz | Prawie nigdy | System traci spójność |
Dobra zasada: layout (marginesy, pozycja w siatce) można korygować z zewnątrz, ale wygląd (kolory, typografia, zaokrąglenia) pochodzi z tokenów i wariantów komponentu.
Najczęstszy błąd to doklejanie !important, bo komponent „nie chce" zmienić koloru. Zamiast tego sprawdź, czy brakuje wariantu albo tokena. !important zwykle oznacza, że system nie przewidział przypadku, który właśnie napotkałeś, a to sygnał, żeby rozszerzyć system, a nie go obchodzić.
Dlaczego dostępność jest częścią design systemu?
Design system jest idealnym miejscem na dostępność, bo poprawisz ją raz i zadziała w całym produkcie. Jeśli komponent Button ma poprawny kontrast, widoczny fokus i obsługę klawiatury, każdy ekran, który go używa, dziedziczy te cechy. Odwrotnie też: błąd w komponencie rozchodzi się wszędzie.
Warto więc od początku włączyć do systemu: kontrast kolorów w tokenach, widoczny stan :focus, semantyczne elementy HTML (<button>, a nie <div onClick>) oraz opisy dla czytników ekranu.
O tym, dlaczego dostępność to dziś nie tylko dobra praktyka, ale też wymóg prawny (EAA), i jak wdrożyć ją w zespole IT, rozmawiamy z Żanetą Sochoń:
Kiedy design system się opłaca, a kiedy to przerost formy?
Design system ma sens, gdy:
- w produkcie pracuje kilka osób lub kilka zespołów, które budują ekrany równolegle,
- masz więcej niż jeden produkt lub aplikację o wspólnej marce,
- ten sam element (formularz, przycisk, tabela) pojawia się w wielu miejscach i zaczyna się rozjeżdżać.
Natomiast w małym projekcie jednoosobowym, prostym landing page'u albo prototypie pełny system z dokumentacją i wersjonowaniem jest zwykle przerostem formy. Wystarczą wtedy tokeny w CSS i kilka dobrze nazwanych komponentów. Zacznij od tego, a rozbudowuj system dopiero wtedy, gdy zaczniesz widzieć powtarzające się problemy ze spójnością.
Praktyczna zasada na start: nie wyciągaj fragmentu UI do osobnego komponentu systemu, dopóki nie pojawi się w trzech miejscach. Wcześniej tworzysz abstrakcję, której jeszcze nie rozumiesz, i bardzo prawdopodobne, że po pierwszej zmianie wymagań trzeba będzie ją przepisać.
Co zapamiętać?
- Design system to nie biblioteka komponentów, tylko tokeny + komponenty + wzorce + dokumentacja.
- Zaczynaj od tokenów jako zmiennych CSS i nazywaj je według roli (
--color-primary), a nie wyglądu (--blue). - Dokumentuj komponenty (np. w Storybooku), bo bez dokumentacji system nie działa.
- Ogranicz sposoby nadpisywania stylów: wariant lub token, nie
!important. - Wbuduj dostępność w komponenty, bo skorzysta z niej cały produkt.
Jeśli chcesz nauczyć się budować takie interfejsy w praktyce, z code review i wsparciem mentora, zobacz naukę frontendu z mentorem.


