# Što ćete izgraditi i zašto je to važno#
Dizajn sustav u Next.js proizvodu nije Figma biblioteka. To je verzionirani UI paket s tokenima, komponentama, zadanim postavkama pristupačnosti, dokumentacijom i testovima koji nameću konzistentnost.
Ovaj vodič pokazuje pristup od početka do kraja za izgradnju stacka Next.js design system Storybook Tailwind Radix koji se skalira: tokeni i theming, komponente bazirane na Radixu, Storybook dokumentacija, integracija u product codebase i governance kroz lintanje i vizualnu regresiju.
Ako želite dublju pozadinu o tokenima i arhitekturi, prvo pročitajte:
# Pregled arhitekture: Monorepo, paketi i tok izdanja#
Najbrži način da dizajn sustav ostane upotrebljiv jest da ga je lako konzumirati, a teško pogrešno koristiti. Monorepo s workspace paketima obično postiže taj balans.
Preporučena struktura projekta#
| Putanja | Svrha | Napomene |
|---|---|---|
apps/web | Next.js aplikacija proizvoda | Koristi samo @acme/ui komponente |
packages/ui | Komponente dizajn sustava | Radix primitive, Tailwind stiliziranje, exportani API |
packages/tokens | Tokeni i Tailwind preset | Centralni source of truth za boje, razmake, radijuse |
packages/eslint-config | Pravila konzistentnosti | Ograniči raw HTML, nametni granice importa |
packages/storybook | Storybook host | Može biti unutar packages/ui ili kao zasebna app |
Ova struktura podržava:
- Neovisno verzioniranje
@acme/ui - Dijeljene tokene između aplikacija
- Centralizirana lint pravila
- Storybook kao jedinstenu dokumentacijsku stranicu
Odabiri alata koji dobro rade u 2026.#
| Područje | Preporuka | Zašto |
|---|---|---|
| Workspaces | pnpm | Brze instalacije, stroga izolacija dependencija |
| Build sustav | Turborepo | Cache i paralelni buildovi za CI |
| Versioning | Changesets | Predvidljiva izdanja i generiranje changeloga |
| Vizualna regresija | Chromatic ili Playwright screenshotovi | Hvata UI drift prije deploya |
| A11y automatizacija | Storybook a11y + testing-library | Rano pronalazi česte WCAG probleme |
🎯 Ključna poruka: Dizajn sustav tretirajte kao proizvod: verzionirajte ga, testirajte, dokumentirajte i provodite kroz CI.
# Korak 1: Design tokeni kao izvor istine#
Tokeni moraju biti upotrebljivi i iz Tailwind utilityja i iz stilova komponenti. Praktičan pristup je definirati tokene u CSS varijablama, a zatim mapirati Tailwind na te varijable.
Strategija tokena: CSS varijable plus Tailwind mapiranje#
Koristite semantičke tokene poput --color-bg, a ne sirove vrijednosti poput --blue-500. Semantički tokeni podržavaju theming i smanjuju refaktore.
Primjer token datoteke u packages/tokens/src/styles/tokens.css:
:root {
--color-bg: 255 255 255;
--color-fg: 17 24 39;
--color-primary: 59 130 246;
--radius-sm: 6px;
--radius-md: 10px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
}
[data-theme="dark"] {
--color-bg: 17 24 39;
--color-fg: 243 244 246;
--color-primary: 96 165 250;
}Koristite RGB triplete kako bi Tailwind mogao primijeniti opacity putem rgb(var(--token) / alpha).
Tailwind preset u tokens paketu#
Kreirajte packages/tokens/src/tailwind-preset.ts:
import type { Config } from "tailwindcss";
export const preset: Config = {
theme: {
extend: {
colors: {
bg: "rgb(var(--color-bg) / <alpha-value>)",
fg: "rgb(var(--color-fg) / <alpha-value>)",
primary: "rgb(var(--color-primary) / <alpha-value>)",
},
borderRadius: {
sm: "var(--radius-sm)",
md: "var(--radius-md)",
},
spacing: {
1: "var(--space-1)",
2: "var(--space-2)",
3: "var(--space-3)",
},
},
},
};Objavite tokens.css i Tailwind preset iz @acme/tokens. Vaša product app i @acme/ui oba ovise o njemu.
💡 Savjet: Držite površinu tokena malom. Većini timova treba manje od 10 semantičkih tokena boja po temi da bi se brzo kretali bez stalnih rasprava o tokenima.
Učitavanje tokena u Next.js#
U apps/web/src/app/layout.tsx importajte kompajlirani CSS iz tokena:
import "@acme/tokens/tokens.css";
import "./globals.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" data-theme="light">
<body className="bg-bg text-fg">{children}</body>
</html>
);
}Postavite data-theme prema korisničkoj preferenciji ili sistemskoj postavci, ali ga držite determinističkim za SSR.
# Korak 2: Gradite komponente na Radix UI primitivima#
Radix UI rješava teške dijelove: focus trapove, navigaciju tipkovnicom, ARIA atribute i ponašanje tipke Escape. Tailwind rješava vizualni sloj. Vaš dizajn sustav treba wrapati Radix u konzistentan API.
Konvencije komponenti koje se skaliraju#
Definirajte nekoliko pravila i provodite ih:
- Exportajte isključivo iz jedne ulazne točke, npr.
@acme/ui. - Svaka mapa komponente uključuje
index.ts,*.tsxi*.stories.tsx. - Koristite
cnhelper za spajanje Tailwind klasa. - Ne izlažite Radix internals osim kad je potrebno.
Primjer cn helpera u packages/ui/src/lib/cn.ts:
export function cn(...classes: Array<string | undefined | false>) {
return classes.filter(Boolean).join(" ");
}Primjer komponente: Button#
Button postavlja baseline za sve komponente: veličine, stanja, focus ring i ponašanje disabled stanja.
import * as React from "react";
import { cn } from "../lib/cn";
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: "primary" | "secondary" | "ghost";
size?: "sm" | "md";
};
export function Button({
className,
variant = "primary",
size = "md",
...props
}: ButtonProps) {
const base =
"inline-flex items-center justify-center rounded-md font-medium " +
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary " +
"disabled:opacity-50 disabled:pointer-events-none";
const variants: Record<string, string> = {
primary: "bg-primary text-white hover:opacity-90",
secondary: "bg-fg/10 text-fg hover:bg-fg/15",
ghost: "bg-transparent text-fg hover:bg-fg/10",
};
const sizes: Record<string, string> = {
sm: "h-9 px-3 text-sm",
md: "h-10 px-4 text-sm",
};
return (
<button
className={cn(base, variants[variant], sizes[size], className)}
{...props}
/>
);
}Ovo vam daje:
- Konzistentan focus ring kroz cijeli proizvod
- Predvidljive tokenizirane veličine
- Stroge varijante koje sprječavaju “one-off” stiliziranje
Primjer komponente: Dialog pomoću Radixa#
Dialog je mjesto gdje Radix odmah donosi vrijednost. Bugovi u upravljanju fokusom su skupi i često prođu neopaženo u produkciju.
import * as Dialog from "@radix-ui/react-dialog";
import { cn } from "../lib/cn";
export function Modal({
title,
children,
open,
onOpenChange,
}: {
title: string;
children: React.ReactNode;
open: boolean;
onOpenChange: (open: boolean) => void;
}) {
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50" />
<Dialog.Content
className={cn(
"fixed left-1/2 top-1/2 w-[90vw] max-w-lg -translate-x-1/2 -translate-y-1/2",
"rounded-md bg-bg p-6 shadow-lg focus-visible:outline-none"
)}
>
<Dialog.Title className="text-lg font-semibold">{title}</Dialog.Title>
<div className="mt-3">{children}</div>
<Dialog.Close className="mt-6 inline-flex h-10 px-4 items-center rounded-md bg-fg/10">
Close
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}⚠️ Upozorenje: Nemojte uklanjati Radix zadana ponašanja poput focus trappinga ili escape-to-close kako biste uskladili “pixel-perfect” prototip. Ta ponašanja su zahtjevi pristupačnosti, a njihovo uklanjanje povećava troškove podrške i pravni rizik.
# Korak 3: Zadane postavke pristupačnosti koje ne ovise o disciplini developera#
Većina regresija pristupačnosti dolazi iz nedosljednih defaulta: nedostajuće labele, pokvareni focus stilovi i pogrešna semantika. Dizajn sustav treba smanjiti broj odluka koje developeri moraju donositi.
A11y checklist ugrađen u komponente#
| Područje komponente | Default | Zašto je važno |
|---|---|---|
| Focus stilovi | focus-visible:ring-2 | Korisnicima tipkovnice treba jasan indikator fokusa |
| Labele | Zahtijevajte aria-label kad nema vidljive labele | Screen readerima trebaju pristupačna imena |
| Disabled stanje | disabled:pointer-events-none plus opacity | Sprječava slučajne klikove i razjašnjava stanje |
| Hit targeti | Minimalna visina h-10 za gumbe | Poboljšava upotrebljivost na mobitelu |
| Dijalozi i izbornici | Radix primitive | Ispravan roving tabindex i upravljanje fokusom |
Praktično pravilo: “Nema icon-only gumba bez labele”#
Olakšajte to tako da ponudite IconButton komponentu koja zahtijeva label prop.
import * as React from "react";
import { Button } from "./button";
export function IconButton(
props: Omit<React.ComponentProps<typeof Button>, "children"> & {
label: string;
icon: React.ReactNode;
}
) {
const { label, icon, ...rest } = props;
return (
<Button aria-label={label} {...rest}>
{icon}
</Button>
);
}Ovo sprječava jedan od najčešćih pristupačnih “failova” u produkcijskim sučeljima.
# Korak 4: Storybook kao dokumentacija, playground i test runner#
Storybook je mjesto gdje dizajn sustav postaje upotrebljiv kroz timove. Smanjuje pitanja na Slacku, ubrzava review i djeluje kao UI test harness.
Osnove Storybook postavki#
Instalirajte dependencije u Storybook host paketu, a zatim osigurajte da importate tokene.
U .storybook/preview.ts:
import "@acme/tokens/tokens.css";
import type { Preview } from "@storybook/react";
const preview: Preview = {
parameters: {
controls: { expanded: true },
a11y: { test: "todo" },
},
};
export default preview;Dobra priča je primjer korištenja, a ne snapshot#
Priča bi trebala:
- Prikazati varijante i veličine
- Dati kontrole za ključne propove
- Demonstrirati “stvaran” sadržaj, ne samo lorem ipsum
Primjer button.stories.tsx:
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./button";
const meta: Meta<typeof Button> = {
title: "Core/Button",
component: Button,
args: { children: "Continue", variant: "primary", size: "md" },
};
export default meta;
export const Primary: StoryObj<typeof Button> = {};
export const Secondary: StoryObj<typeof Button> = {
args: { variant: "secondary" },
};
export const Disabled: StoryObj<typeof Button> = {
args: { disabled: true },
};ℹ️ Napomena: Za mnoge timove, Storybook zamjenjuje zasebnu web stranicu dizajn sustava. Ušteda vremena u onboardingu i UI reviewu često premaši početni trošak setupa unutar prvih 4 do 8 tjedana.
Storybook interaction testovi#
Interaction testovi hvataju probleme ponašanja poput “Escape zatvara modal” i “Tab ostaje unutar dijaloga”. Takve je probleme skupo loviti isključivo kroz manual QA.
Držite test malenim i fokusiranim:
import type { Meta, StoryObj } from "@storybook/react";
import { within, userEvent } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
import { Modal } from "./modal";
const meta: Meta<typeof Modal> = { title: "Overlays/Modal", component: Modal };
export default meta;
export const OpensAndCloses: StoryObj<typeof Modal> = {
render: () => <Modal title="Title" open={true} onOpenChange={() => {}}>Body</Modal>,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.getByText("Title")).toBeInTheDocument();
},
};# Korak 5: Integrirajte dizajn sustav u Next.js product codebase#
Imate dva cilja integracije:
- 1Učiniti usvajanje jednostavnim
- 2Učiniti zaobilaženje teškim
Checklist za integraciju#
| Područje | Što napraviti | Što sprječava |
|---|---|---|
| Importi | Koristite samo @acme/ui | Nasumične kopije komponenti |
| Tokeni | Učitajte @acme/tokens/tokens.css u root layoutu | Drift boja između aplikacija |
| Tailwind | Koristite tokens preset u app configu | Hardkodirane vrijednosti u utilityjima |
| SSR | Držite temu determinističnom na serveru | Hydration mismatch |
| Migracija | UI mijenjajte postupno | “Big bang” rewrites koji zapnu |
U apps/web/tailwind.config.ts, koristite preset:
import type { Config } from "tailwindcss";
import { preset } from "@acme/tokens/tailwind-preset";
export default {
presets: [preset],
content: ["./src/**/*.{ts,tsx}", "../../packages/ui/src/**/*.{ts,tsx}"],
theme: { extend: {} },
} satisfies Config;Migracijski obrazac koji radi u stvarnim timovima#
Prvo zamijenite UI s velikim churnom:
- Gumbi i linkovi
- Form kontrole
- Modali i dropdownovi
- Tablice i prikaz podataka
Ovo brže smanjuje broj bugova jer se te komponente pojavljuju na mnogo ekrana i imaju puno stanja.
# Korak 6: Nametnite konzistentnost lintanjem i granicama importa#
Konzistentnost se ne skalira samo kroz code review. Automatizirano provođenje je jeftinije i pouzdanije.
ESLint pravila za odvraćanje od zaobilaženja#
Koristite no-restricted-imports kako biste blokirali direktne Radix importe u product codeu, i time prisilili @acme/ui wrapere:
module.exports = {
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "@radix-ui/react-dialog",
message: "Use Modal from @acme/ui instead of importing Radix directly.",
},
],
},
],
},
};Dodajte ograničenja za raw HTML elemente kad je prikladno. Primjerice, zabranite raw button u app codeu kako biste osigurali konzistentne focus stilove i varijante. Ovo primjenjujte pažljivo kako ne biste usporili legitimne slučajeve.
💡 Savjet: Neka pravilo bude opt-in po folderu. Prvo ga strogo provodite u
apps/web/src/features, a dopustite iznimke uapps/web/src/playgroundili migracijskim folderima.
Pravila konzistentnosti Tailwind klasa#
Ako dopustite proizvoljno korištenje Tailwinda, dobit ćete nekonzistentan UI. Cilj nije zabraniti Tailwind, nego ga ograničiti na odobrene tokene i obrasce.
Česte opcije provođenja:
- Ograničiti određene klase poput
text-red-500u app codeu, i umjesto toga zahtijevati semantičke tokene. - Zahtijevati
bg-bg,text-fgi semantičke boje umjesto palette boja.
Ovdje se pristup iz najboljih praksi za Tailwind CSS isplati: manje custom overrideova utilityja i manje “one-off” stringova klasa.
# Korak 7: Testiranje vizualne regresije i CI gateovi#
Unit testovi propuštaju UI probleme poput razmaka, boja i z-index slojevanja. Testovi vizualne regresije to pouzdano hvataju.
Dvije praktične opcije#
| Opcija | Trud oko postavljanja | Trajni trošak | Najbolje za |
|---|---|---|---|
| Chromatic | Nizak | Plaćeno na skali | Timove koji žele brzinu i hostane diffove |
| Playwright screenshotovi | Srednji | Compute vrijeme u CI-ju | Timove koji žele potpunu kontrolu i self-hosting |
Realističan baseline je snapshotati:
- Varijante gumba
- Stanja form fieldova
- Layering modala i popovera
- Dark mode teme
Minimalni primjer Playwright screenshot testa#
Kreirajte story route i snapshotajte renderani story iframe. Neka bude jednostavno i malo po testu.
import { test, expect } from "@playwright/test";
test("Button primary visual", async ({ page }) => {
await page.goto("http://localhost:6006/?path=/story/core-button--primary");
await expect(page).toHaveScreenshot("button-primary.png", { fullPage: true });
});Ovo odmah hvata promjene u paddingu, border radiusu i tokenima boja.
⚠️ Upozorenje: Vizualna regresija radi samo ako su okoline deterministične. Pinajte fontove, postavite konzistentan viewport i izbjegavajte time-based UI u pričama.
# Korak 8: Verzioniranje, izdanja i kompatibilnost unatrag#
Dizajn sustav koji se ne može sigurno isporučivati bit će ignoriran. Verzioniranje i changelogovi su ono što čini usvajanje predvidljivim.
Koristite semantičko verzioniranje s Changesets#
Praktična politika:
- Patch izdanja: bug fixovi, interni refaktori
- Minor izdanja: nove komponente, novi propovi s defaultima
- Major izdanja: breaking API, preimenovanje tokena, promjena ponašanja
Checklist izdanja po promjeni:
- Ažurirane priče
- Ažurirana dokumentacija u Storybooku
- Vizualni snapshotovi ažurirani i odobreni
- Dodana Changeset datoteka
Primjer changeset datoteke:
---
"@acme/ui": minor
---
Add secondary variant to Button and document usage in Storybook.Strategija depreciranja za stvarne proizvode#
Breaking promjene stvaraju churn. Preferirajte deprecacije s vremenskim okvirom:
- Ostavite stari prop da radi 1 do 2 minor verzije
- Dodajte lint pravilo upozorenja za deprecated korištenje
- Osigurajte codemodove za mehaničke migracije
Ako se dizajn sustav koristi kroz više aplikacija, ovaj pristup može uštedjeti dane rada po kvartalu.
# Ključni zaključci#
- Definirajte tokene kao semantičke CSS varijable i mapirajte Tailwind na njih kako bi teme i utilityji ostali konzistentni.
- Wrapajte Radix UI primitive vlastitim API-jem kako biste standardizirali pristupačnost, focus stilove i varijante.
- Koristite Storybook kao dokumentaciju i test harness s a11y provjerama i interaction testovima, ne samo kao galeriju.
- Integrirajte kroz workspace pakete i Tailwind presete, zatim prvo migrirajte UI s najvećim churnom kako biste brzo dobili ROI.
- Nametnite korištenje lint pravilima i granicama importa, a UI drift hvatajte vizualnom regresijom u CI-ju.
- Verzionirajte dizajn sustav s Changesets i semantičkim verzioniranjem, uz deprecacije kako ne biste razbijali produkt timove.
# Zaključak#
Produkcijski spreman dizajn sustav u Next.js je pipeline: tokeni pogone Tailwind, Radix daje pristupačno ponašanje, Storybook dokumentira i testira, a CI provodi konzistentnost kroz lintanje i vizualnu regresiju.
Ako želite pomoć u dizajnu modela tokena, izradi komponenti baziranih na Radixu ili postavljanju Storybooka plus CI gateova za Next.js proizvod, kontaktirajte Samioda i možemo implementirati verzionirani dizajn sustav koji vaš tim može isporučivati s pouzdanjem.
FAQ
Osnivač i senior developer u Samiodi. 8+ godina iskustva u izradi React, Next.js, Flutter i n8n rješenja za klijente diljem Europe.
Više iz kategorije Web razvoj
Sve →Kontrolna lista za React code review koju koristimo: performanse, pristupačnost i održivost
Praktična kontrolna lista za React code review usmjerena na performanse, pristupačnost i održivost, s primjerima, savjetima za automatizaciju i copy-paste predloškom.
Next.js višeregionalne implementacije: obrasci za nižu latenciju na Vercelu i Cloudflareu
Praktičan vodič za Next.js višeregionalnu implementaciju u 2026.: edge rendering, regionalni SSR i obrasci lokalnosti podataka na Vercelu i Cloudflareu, uključujući upozorenja za baze podataka i kontrolnu listu za provjeru latencije.
Next.js monorepo s Turborepo + pnpm Workspaces: praktičan vodič za postavljanje u 2026.
Postavite monorepo spreman za produkciju za Next.js koristeći Turborepo i pnpm workspaces, sa zajedničkim paketima, lintanjem, testiranjem i CI cacheiranjem — uz trade-offove i zamke koje treba izbjeći.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Izgradnja React dizajn sustava s dizajnerskim tokenima: Tailwind CSS + Radix UI + TypeScript
Praktičan vodič za 2026. o izgradnji React dizajn sustava s Tailwindom i Radixom uz dizajnerske tokene, pristupačne primitive, tematiziranje i ponovno iskoristive pakete kroz više aplikacija.
Kontrolna lista za React code review koju koristimo: performanse, pristupačnost i održivost
Praktična kontrolna lista za React code review usmjerena na performanse, pristupačnost i održivost, s primjerima, savjetima za automatizaciju i copy-paste predloškom.
Next.js monorepo s Turborepo + pnpm Workspaces: praktičan vodič za postavljanje u 2026.
Postavite monorepo spreman za produkciju za Next.js koristeći Turborepo i pnpm workspaces, sa zajedničkim paketima, lintanjem, testiranjem i CI cacheiranjem — uz trade-offove i zamke koje treba izbjeći.