#Дизайн-системи: як створити масштабовану UI-бібліотеку
Дизайн-система — це не просто набір кнопок і полів вводу. Це єдине джерело правди для всього візуального і інтерактивного в продукті. У CyberWolf.Studio ми створили десятки дизайн-систем для продуктів різного масштабу, і ділимося нашим підходом.
##Навіщо потрібна дизайн-система
Без системного підходу UI неминуче деградує:
- 12 відтінків сірого замість структурованої палітри
- 5 різних кнопок для однакової дії
- Різна типографіка на кожній сторінці
- Неконсистентні відступи між елементами
Дизайн-система вирішує ці проблеми системно, забезпечуючи спільну мову між дизайнерами та розробниками.
##Крок 1: Design Tokens
Токени — фундамент системи. Це абстрактний шар між дизайн-рішеннями та їх реалізацією:
1/* Семантичні CSS-змінні */2:root {3 /* Кольори */4 --color-background: 0 0% 100%;5 --color-foreground: 222 47% 11%;6 --color-accent: 262 83% 58%;7 --color-muted: 215 16% 47%;8 --color-border: 214 32% 91%;9 --color-destructive: 0 84% 60%;10 --color-success: 142 71% 45%;1112 /* Типографіка */13 --font-sans: "Inter", system-ui, sans-serif;14 --font-mono: "JetBrains Mono", monospace;1516 /* Відступи (базовий крок: 4px) */17 --space-1: 0.25rem;18 --space-2: 0.5rem;19 --space-3: 0.75rem;20 --space-4: 1rem;21 --space-6: 1.5rem;22 --space-8: 2rem;23 --space-12: 3rem;24 --space-16: 4rem;2526 /* Радіуси */27 --radius-sm: 0.25rem;28 --radius-md: 0.5rem;29 --radius-lg: 0.75rem;30 --radius-full: 9999px;3132 /* Тіні */33 --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);34 --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);35 --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);36}3738/* Темна тема — перевизначення токенів */39[data-theme="dark"] {40 --color-background: 222 47% 11%;41 --color-foreground: 210 40% 98%;42 --color-border: 217 33% 17%;43}
##Крок 2: Базові компоненти
Будуємо знизу вгору — від найпростіших до складних:
###Кнопка — найчастіше використовуваний компонент
1import { cva, type VariantProps } from "class-variance-authority"23const buttonVariants = cva(4 // Базові стилі5 "inline-flex items-center justify-center font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",6 {7 variants: {8 variant: {9 primary: "bg-accent text-white hover:bg-accent/90",10 secondary: "border border-border bg-transparent hover:bg-muted/10",11 ghost: "hover:bg-muted/10",12 destructive: "bg-destructive text-white hover:bg-destructive/90",13 link: "text-accent underline-offset-4 hover:underline",14 },15 size: {16 sm: "h-8 px-3 text-xs",17 md: "h-10 px-4 text-sm",18 lg: "h-12 px-6 text-base",19 icon: "h-10 w-10",20 },21 },22 defaultVariants: {23 variant: "primary",24 size: "md",25 },26 }27)2829interface ButtonProps30 extends React.ButtonHTMLAttributes<HTMLButtonElement>,31 VariantProps<typeof buttonVariants> {32 loading?: boolean33}3435export function Button({ variant, size, loading, children, ...props }: ButtonProps) {36 return (37 <button className={buttonVariants({ variant, size })} disabled={loading} {...props}>38 {loading && <Spinner className="mr-2 h-4 w-4 animate-spin" />}39 {children}40 </button>41 )42}
###Input — з вбудованою підтримкою станів
1interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {2 label: string3 error?: string4 hint?: string5}67export function Input({ label, error, hint, id, ...props }: InputProps) {8 const inputId = id || label.toLowerCase().replace(/\s+/g, "-")910 return (11 <div className="space-y-1.5">12 <label htmlFor={inputId} className="text-sm font-medium text-foreground">13 {label}14 </label>15 <input16 id={inputId}17 className={cn(18 "h-10 w-full border bg-background px-3 text-sm transition-colors",19 "placeholder:text-muted focus:outline-none focus:ring-2 focus:ring-accent",20 error ? "border-destructive" : "border-border"21 )}22 aria-invalid={!!error}23 aria-describedby={error ? `${inputId}-error` : undefined}24 {...props}25 />26 {error && (27 <p id={`${inputId}-error`} className="text-xs text-destructive">28 {error}29 </p>30 )}31 {hint && !error && (32 <p className="text-xs text-muted">{hint}</p>33 )}34 </div>35 )36}
##Крок 3: Складені компоненти
Комбінуємо прості компоненти у більш складні патерни:
| Рівень | Приклади | Характеристика |
|---|---|---|
| Атоми | Button, Input, Badge, Avatar | Один HTML-елемент або мінімальна композиція |
| Молекули | FormField, SearchBar, MenuItem | Комбінація 2-3 атомів |
| Організми | DataTable, NavigationBar, Card | Самостійні секції інтерфейсу |
| Шаблони | DashboardLayout, AuthLayout | Структура цілої сторінки |
##Крок 4: Забезпечення якості
###Accessibility (a11y)
Кожен компонент перевіряється на доступність:
- Коректна семантика HTML
- Підтримка клавіатурної навігації
- ARIA-атрибути де потрібно
- Контрастність кольорів (WCAG 2.1 AA)
###Візуальне тестування
1// Chromatic для автоматичного visual regression testing2// Кожен PR автоматично перевіряється на візуальні зміни3test("Button renders correctly", async ({ page }) => {4 await page.goto("/storybook/?path=/story/button--primary")5 await expect(page.locator(".button")).toHaveScreenshot("button-primary.png")6})
""Дизайн-система — це не проєкт з дедлайном. Це процес, який живе разом з продуктом."
##Підсумки
Створення дизайн-системи — це інвестиція, яка окупається з кожним новим компонентом і кожною новою сторінкою. Починайте з малого — токени, кілька базових компонентів, документація. Потім рости органічно, додаючи те, що реально потрібно продукту.
Потрібна дизайн-система для вашого продукту? Зв'яжіться з нами — створимо її разом.