# Što ćete izgraditi#
Izgradit ćete React dizajn sustav s Tailwindom i Radixom koji se može ponovno koristiti kroz više aplikacija, koji podržava teme, pristupačan je po defaultu i verzionira se kao pravi proizvod.
Implementirat ćete:
- Dizajnerske tokene za boje, razmake i tipografiju kao CSS varijable.
- Strategiju tematiziranja koja radi kroz React i Next.js aplikacije.
- Pristupačne primitive uz Radix UI, omotane u opinionated komponente.
- Pakiranje i distribuciju za ponovno korištenje, plus konvencije za verzioniranje i dokumentaciju.
Ako želite dublje arhitekturne obrasce, kombinirajte ovaj vodič s Arhitektura React komponenti za skalabilan dizajn sustav. Za Tailwind konvencije korištene u nastavku pogledajte Tailwind CSS najbolje prakse. Ako ovo integrirate u produktnu aplikaciju, Početak s Next.js pokriva osnovno postavljanje.
# Preduvjeti#
| Zahtjev | Verzija | Napomene |
|---|---|---|
| Node.js | 18 ili 20 | LTS preporučen za monorepo |
| React | 18+ | Radix i moderni alati pretpostavljaju React 18 |
| TypeScript | 5+ | Za satisfies, bolju type inference |
| Tailwind CSS | 3.4+ | Tematiziranje temeljeno na varijablama dobro radi |
| Radix UI | latest | Koristite primitive po paketu komponenti |
| Package manager | pnpm preporučen | Brži workspaces i stabilniji lockfile |
# Preporučeni monorepo raspored#
Dizajn sustav je proizvod. Tretirajte ga tako: odvojeni paketi, strogi API-ji, automatizirana izdanja i predvidljiv build.
Praktičan raspored za više aplikacija:
| Putanja | Svrha | Objavljuje se |
|---|---|---|
apps/web | Next.js ili React aplikacija koja koristi dizajn sustav | Ne |
apps/admin | Druga aplikacija koja koristi isti UI paket | Ne |
packages/tokens | Izvor dizajnerskih tokena i generirane CSS varijable | Da |
packages/ui | React komponente na Radix + Tailwind | Da |
packages/config | Zajednički alati, Tailwind preset, ESLint pravila | Opcionalno |
Zašto odvojiti tokene od UI-ja#
Tokeni su temelj i trebaju biti framework-agnostički. Kasnije ih možete koristiti na webu, u email predlošcima, dokumentaciji ili čak na mobilnim platformama. Držanje tokena u zasebnom paketu smanjuje spregu i čini ih lakšima za audit.
# Korak 1: Definirajte dizajnerske tokene kao CSS varijable#
Token nije “plava 500”. Token je “primary” i može mapirati na različite vrijednosti po temi. Ključno je odvojiti značenje od vrijednosti.
Jednostavan model tokena:
- Semantički tokeni:
--color-bg,--color-fg,--color-primary,--color-danger - Tokeni na razini komponente samo kada treba:
--button-bg,--card-radius - Tokeni skale: koraci razmaka, veličine fonta, radijusi, sjene
Tokeni boja: prvo semantika#
Kreirajte packages/tokens/src/tokens.css:
:root {
/* Base */
--color-bg: 0 0% 100%;
--color-fg: 222 47% 11%;
/* Brand */
--color-primary: 222 89% 55%;
--color-primary-fg: 0 0% 100%;
/* Surfaces */
--color-muted: 210 40% 96%;
--color-muted-fg: 215 16% 47%;
/* Feedback */
--color-danger: 0 84% 60%;
--color-danger-fg: 0 0% 100%;
/* Borders and focus */
--color-border: 214 32% 91%;
--color-ring: 222 89% 55%;
/* Radii */
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
/* Spacing scale */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
--space-8: 32px;
/* Typography */
--font-sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
--text-xs: 12px;
--text-sm: 14px;
--text-md: 16px;
--text-lg: 18px;
--text-xl: 20px;
--leading-tight: 1.2;
--leading-normal: 1.5;
}
:root[data-theme="dark"] {
--color-bg: 222 47% 11%;
--color-fg: 210 40% 98%;
--color-muted: 217 33% 17%;
--color-muted-fg: 215 20% 65%;
--color-border: 217 33% 17%;
--color-ring: 222 89% 65%;
}Ovo koristi HSL komponente kako bi Tailwind mogao čisto primijeniti alpha preko hsl(var(--token) / 0.8).
💡 Savjet: Držite tokene malima i semantičkima. Timovi koji krenu s 200+ tokena boja obično završe s nedosljednim nazivljem i dupliciranom upotrebom. Krenite s 20 do 40 semantičkih tokena i dodajte nove tek kada se pojavi stvarna UI potreba.
Tokeni razmaka: skala je bolja od proizvoljnih brojeva#
Radit ćete brže ako su razmaci predvidljivi. Čest neuspjeh dizajn sustava je “samo stavi padding koji dobro izgleda”.
Koristite skalu koju možete zapamtiti:
--space-1do--space-8- izvedite paddings komponenti iz te skale
Tokeni tipografije: kodirajte namjeru, ne samo veličine#
Tokeni poput --text-md su u redu, ali skalabilnija konvencija su tokeni temeljeni na ulozi poput --text-body, --text-caption, --text-heading. Ako vaš proizvod ima samo jedan tipografski stil, krenite s veličinama i proširite kasnije.
# Korak 2: Mapirajte tokene u Tailwind (bez dupliciranja vrijednosti)#
Tailwind treba referencirati tokene. Ne smije ponovno “posjedovati” vrijednosti tokena. To smanjuje divergenciju i čini promjenu teme automatskom.
U packages/config/tailwind-preset.ts:
import type { Config } from "tailwindcss";
export const preset = {
theme: {
extend: {
colors: {
bg: "hsl(var(--color-bg) / <alpha-value>)",
fg: "hsl(var(--color-fg) / <alpha-value>)",
primary: "hsl(var(--color-primary) / <alpha-value>)",
"primary-fg": "hsl(var(--color-primary-fg) / <alpha-value>)",
muted: "hsl(var(--color-muted) / <alpha-value>)",
"muted-fg": "hsl(var(--color-muted-fg) / <alpha-value>)",
border: "hsl(var(--color-border) / <alpha-value>)",
ring: "hsl(var(--color-ring) / <alpha-value>)",
danger: "hsl(var(--color-danger) / <alpha-value>)",
"danger-fg": "hsl(var(--color-danger-fg) / <alpha-value>)",
},
borderRadius: {
sm: "var(--radius-sm)",
md: "var(--radius-md)",
lg: "var(--radius-lg)",
},
spacing: {
1: "var(--space-1)",
2: "var(--space-2)",
3: "var(--space-3)",
4: "var(--space-4)",
6: "var(--space-6)",
8: "var(--space-8)",
},
fontFamily: {
sans: "var(--font-sans)",
},
fontSize: {
xs: ["var(--text-xs)", { lineHeight: "var(--leading-normal)" }],
sm: ["var(--text-sm)", { lineHeight: "var(--leading-normal)" }],
md: ["var(--text-md)", { lineHeight: "var(--leading-normal)" }],
lg: ["var(--text-lg)", { lineHeight: "var(--leading-normal)" }],
xl: ["var(--text-xl)", { lineHeight: "var(--leading-tight)" }],
},
},
},
} satisfies Config;Ovo omogućuje korištenje poput:
bg-bg text-fgbg-primary text-primary-fgborder-border ring-ringp-4 gap-2 rounded-md
Učinite preset potrošnim u aplikacijama#
U Tailwind configu svake aplikacije uključite preset i skenirajte datoteke UI paketa. U apps/web/tailwind.config.ts:
import type { Config } from "tailwindcss";
import { preset } from "@acme/config/tailwind-preset";
export default {
presets: [preset],
content: [
"./src/**/*.{ts,tsx}",
"../../packages/ui/src/**/*.{ts,tsx}",
],
} satisfies Config;⚠️ Upozorenje: Ako zaboravite uključiti putanje do UI paketa u Tailwind
content, production build će tree-shakeati klase vaših komponenti. To je jedan od najčešćih uzroka “radi lokalno, ali ne radi u CI-ju”.
# Korak 3: Dodajte tematiziranje koje radi kroz aplikacije#
Tematiziranje pogonjeno tokenima se svodi na prebacivanje selektora teme i prepuštanje posla CSS varijablama.
Strategija prebacivanja teme#
Koristite data atribut na korijenu dokumenta:
- Svijetla tema:
data-theme="light"ili bez atributa - Tamna tema:
data-theme="dark"
U Next.js-u ga postavite na razini html elementa kako biste izbjegli “flash”. Točan mehanizam ovisi o vašem stacku, ali dizajn sustav treba zahtijevati samo atribut, ne i specifičnu biblioteku za teme.
Minimalno client-side prebacivanje u React aplikaciji:
export function setTheme(theme: "light" | "dark") {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("theme", theme);
}Zašto je ovo važno za dizajn sustave#
Kad su tokeni varijable, a Tailwind pokazuje na varijable, isti kod komponente renderira:
- Tamnu temu bez drugog seta klasa
- Brand temu zamjenom nekolicine vrijednosti tokena
- Theming po tenant-u u multi-tenant aplikacijama
Tako izbjegavate dupliciranje varijanti komponenti i napuhavanje API-ja.
# Korak 4: Izgradite pristupačne primitive s Radix UI#
Radix UI vam daje pristupačno ponašanje, upravljanje fokusom, interakcije tipkovnicom i ARIA atribute. Vaš posao je dodati stilove, varijante i dosljedne API-je.
Dobro pravilo: koristite Radix za ponašanje i semantiku, Tailwind za izgled, a TypeScript za ograničenja.
Konvencije komponenti koje trebate standardizirati#
Ove konvencije sprječavaju drift API-ja kroz komponente:
| Konvencija | Preporučeni standard | Zašto je važno |
|---|---|---|
| Stiliziranje | className spojen putem utility funkcije | Dosljedan “escape hatch” |
| Varijante | props variant i size | Predvidljiva upotreba kroz UI |
| Kompozicija | podrška za asChild kad ima smisla | Omogućuje render kao a, button, itd. |
| Prosljeđivanje ref-a | React.forwardRef za interaktivne elemente | Potrebno za Radix i forme |
| Data atributi | koristite data-state i data-disabled | Stiliziranje Radix stanja bez JS-a |
Utility: spajanje className#
U packages/ui/src/utils/cn.ts:
export function cn(...classes: Array<string | undefined | false>) {
return classes.filter(Boolean).join(" ");
}Držite jednostavno. Ako trebate rješavanje konflikata za Tailwind, uvedite merge utility kasnije, ali izbjegnite kompleksnost rano.
Primjer: Button komponenta kao osnovni “building block”#
Ovo je namjerno kratko i usmjereno na produkciju.
import * as React from "react";
import { cn } from "../utils/cn";
type ButtonVariant = "primary" | "secondary" | "danger";
type ButtonSize = "sm" | "md";
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = "primary", size = "md", ...props }, ref) => {
const base =
"inline-flex items-center justify-center rounded-md font-medium transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:pointer-events-none";
const variants: Record<ButtonVariant, string> = {
primary: "bg-primary text-primary-fg hover:opacity-90",
secondary: "bg-muted text-fg hover:opacity-90",
danger: "bg-danger text-danger-fg hover:opacity-90",
};
const sizes: Record<ButtonSize, string> = {
sm: "h-9 px-3 text-sm",
md: "h-10 px-4 text-md",
};
return (
<button
ref={ref}
className={cn(base, variants[variant], sizes[size], className)}
{...props}
/>
);
}
);
Button.displayName = "Button";Ovo koristi tokene za boje i ring. Kada se tema promijeni, gumb se mijenja bez novog seta klasa.
Primjer: Dialog s Radix primitivama#
Dialog je savršen Radix primjer jer je pristupačnost netrivijalna za ispravno implementirati.
import * as React from "react";
import * as Dialog from "@radix-ui/react-dialog";
import { cn } from "../utils/cn";
export function Modal({
open,
onOpenChange,
title,
children,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
children: React.ReactNode;
}) {
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-fg/30" />
<Dialog.Content
className={cn(
"fixed left-1/2 top-1/2 w-[min(92vw,520px)] -translate-x-1/2 -translate-y-1/2",
"rounded-lg border border-border bg-bg p-4 text-fg shadow"
)}
>
<Dialog.Title className="text-lg font-semibold">{title}</Dialog.Title>
<div className="mt-3">{children}</div>
<Dialog.Close className="mt-4">
Close
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}Vrijednost pristupačnosti ovdje je opipljiva:
- Escape zatvara dialog.
- Fokus je “zarobljen”.
- Pozadina je inertna za screen readere.
- ARIA uloge i labeli su ispravni.
Ovi detalji su razlog zašto je Radix snažan izbor za temelj dizajn sustava.
ℹ️ Napomena: Radix komponente izlažu stanje preko data atributa poput
data-state="open". Koristite to u Tailwind klasama kada želite animacije ili stiliziranje temeljeno na stanju bez dodavanja React statea.
# Korak 5: Token-first obrasci stiliziranja koji se skaliraju#
Kako UI raste, konvencija s najvećim ROI-jem je “token-first utilities”. Umjesto pisanja boja specifičnih za komponentu, slažete bg-bg text-fg border-border i dodajete varijante samo gdje imaju smisla.
Koristite semantičke utility klase u komponentama#
- Layout:
p-4,gap-2,rounded-md - Surface:
bg-bg,border-border,text-fg - Stanja:
focus-visible:ring-ring,disabled:opacity-50 - Feedback:
text-danger,bg-danger
Ovo sprječava da hard-coded “brand plava” procuri u komponente. Također omogućuje re-branding bez refaktora.
Kada dodati tokene na razini komponente#
Dodajte tokene na razini komponente samo ako:
- Komponenta ima jedinstven zahtjev stiliziranja koji se ne dijeli drugdje.
- Više proizvoda treba različite stilove komponenti uz zadržavanje iste semantičke teme.
Primjer: ako vaš Card treba posebnu sjenu po proizvodu, uvedite --card-shadow umjesto da ugradite Tailwind shadow klasu u komponentu.
# Korak 6: Zapakirajte dizajn sustav za ponovno korištenje kroz aplikacije#
Razlika između foldera s komponentama i dizajn sustava je distribucija i disciplina. Pakiranje vas prisiljava da definirate javni API.
Što izvoziti#
Izložite čistu površinu API-ja:
| Paket | Izvoz | Primjer |
|---|---|---|
@acme/tokens | CSS varijable i dokumentaciju tokena | dist/tokens.css |
@acme/ui | Komponente i tipove | Button, Modal, Input |
@acme/config | Tailwind preset | preset |
Strategija builda#
Držite build dosadnim:
@acme/tokensobjavljuje CSS.@acme/uikompajlira TypeScript i isporučuje CSS kroz Tailwind upotrebu u aplikacijama potrošačima.
Ako želite da UI paket isporuči prebuilt CSS, napravite to tek kada imate jasnu potrebu integracije. Isporuka CSS-a može pomoći potrošačima koji ne koriste Tailwind, ali dodaje kompleksnost oko bundlanja i dedupinga.
Osigurajte da potrošači učitaju tokene#
Dodajte jedan import u globalni CSS aplikacije, npr. u Next.js app/globals.css:
@import "@acme/tokens/tokens.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background: hsl(var(--color-bg));
color: hsl(var(--color-fg));
}# Korak 7: Konvencije verzioniranja koje sprječavaju kaos pri nadogradnjama#
Ako više aplikacija ovisi o vašem UI-ju, verzioniranje postaje operativno pitanje. Semantičko verzioniranje je nužno, ali nije dovoljno. Trebate i pravila što se smatra breaking promjenom.
Preporučena pravila semantičkog verzioniranja#
| Promjena | Bump verzije | Primjer |
|---|---|---|
| Breaking promjena API-ja | Major | Preimenovanje propa, promjena default ponašanja |
| Unatrag kompatibilna značajka | Minor | Nova komponenta ili ne-breaking varijanta |
| Samo bugfix | Patch | Ispravak focus ringa, bug u klasama, bug u tipovima |
Praktične breaking promjene koje mnogi timovi promaše:
- Promjena default vrijednosti
variantilisize. - Promjena značenja tokena čak i ako naziv ostane isti.
- Promjena DOM strukture na koju se testovi oslanjaju.
Automatizirajte s Changesets#
Changesets je dobar default za monorepo jer forsira human-readable release noteove i veže ih uz PR-ove.
Tipičan workflow:
- 1Dodajte changeset datoteku po PR-u koji utječe na objavljene pakete.
- 2Merge u main.
- 3CI objavljuje pakete i generira changelog.
Vaš minimalni standard za svako izdanje:
- Sažetak
- Migracijske napomene za breaking promjene
- Zahvaćeni paketi
🎯 Ključna poruka: Najbrži način da izgubite povjerenje u dizajn sustav je isporučivanje “tihih” breaking promjena. Strogo verzioniranje i changelogovi nisu birokracija, nego pouzdanost proizvoda.
# Korak 8: Dokumentacija koju developeri stvarno koriste#
Dokumentacija treba odgovoriti na “kako ovo sigurno koristiti u produkciji” u manje od dvije minute po komponenti.
Minimalna dokumentacija po komponenti#
| Sekcija | Što uključiti | Primjer |
|---|---|---|
| Svrha | Kada koristiti, kada ne | Smjernice Button vs LinkButton |
| API | Props, defaulti, varijante | variant, size, disabled ponašanje |
| Pristupačnost | Napomene za tipkovnicu i screen readere | Redoslijed fokusa, labeli, očekivanja aria-* |
| Primjeri | Copy-paste isječci | Primary, secondary, destructive upotreba |
| Tokeni | O kojim tokenima ovisi | --color-primary, --color-ring |
Praktičan format dokumentacije#
Držite dokumentaciju u repozitoriju blizu izvora. Čest obrazac:
packages/ui/src/button/button.mdxpackages/ui/src/modal/modal.mdx
Čak i ako kasnije prijeđete na punu docs stranicu, početak blizu koda održava dokumentaciju ažurnom jer se mijenja u istom PR-u.
Uključite primjere korištenja vezane uz Next.js#
Budući da mnogi timovi rade s Next.js, uključite barem jedan primjer po komponenti koji radi s Next.js App Router obrascima. Ako trebate osnovnu strukturu projekta, referencirajte Početak s Next.js.
# Korak 9: Produkcijske provjere: pristupačnost, dosljednost i usvajanje#
Dizajn sustav uspijeva kada je usvajanje jednostavno, a regresije rijetke.
Provjere pristupačnosti koje možete standardizirati#
- Navigacija samo tipkovnicom za sve interaktivne komponente.
- Vidljivo fokus stanje na svim fokusabilnim elementima.
- Focus trapping i escape handling za modal i popover.
- Kontrole forme moraju imati labele, opise i obrasce za error poruke.
Praktičan KPI za pratiti: smanjiti dizajnom povezane bugove. Mnogi timovi navode da standardizacija na pristupačnu biblioteku komponenti značajno smanjuje UI regresije jer se ponašanje više ne re-implementira po featureu.
Provjere dosljednosti#
- U zajedničkim komponentama koristite tokene isključivo za boje i razmake.
- Nema hard-coded hex boja u
@acme/ui. - Varijante i veličine usklađene kroz komponente.
- Put depreciranja za stare komponente, ne nagla uklanjanja.
Za granice komponenti i pravila ponovne upotrebe kroz timove, uskladite se s obrascima iz Arhitektura React komponenti za skalabilan dizajn sustav.
Pravila održivosti Tailwinda#
Ako Tailwind stringovi utility klasa postanu teško čitljivi:
- Izvucite ih u male konstante unutar komponente.
- Izbjegavajte “mega-stringove” klasa koji miješaju layout, stanje i temu u jednom redu.
- Držite varijante eksplicitnima.
Za kompletan checklist, pogledajte Tailwind CSS najbolje prakse.
# Ključne poruke#
- Definirajte dizajnerske tokene kao semantičke CSS varijable i neka ih Tailwind referencira kako bi tematiziranje postalo zamjena tokena, a ne prepisivanje komponenti.
- Koristite Radix UI za pristupačno ponašanje i omotajte primitive u opinionated React komponente s dosljednim obrascima
variant,sizeiclassName. - Zapakirajte tokene, Tailwind preset i UI komponente odvojeno kako bi se više aplikacija moglo nadograđivati neovisno i sigurno.
- Provodite semantičko verzioniranje uz automatizirane changelogove kako biste spriječili tihe breaking promjene u aplikacijama potrošačima.
- Dokumentirajte svaku komponentu sa svrhom, API-jem, napomenama o pristupačnosti, ovisnostima o tokenima i copy-paste primjerima kako biste poboljšali usvajanje.
# Zaključak#
React dizajn sustav s Tailwindom i Radixom postaje održiv kada su tokeni jedini izvor istine, Radix pruža pristupačne primitive, a vaš UI paket isporučuje dosljedan API uz disciplinirano verzioniranje.
Ako želite da vam Samioda pomogne izgraditi ili modernizirati produkcijski dizajn sustav, uključujući arhitekturu tokena, komponente temeljene na Radixu i automatizirana izdanja kroz više aplikacija, javite se putem naše web stranice i definirat ćemo praktičan plan uvođenja.
FAQ
Više iz kategorije Web razvoj
Sve →Next.js + Supabase SaaS početna arhitektura (App Router): Auth, RLS, naplata i multi-tenancy
Production-ready nacrt za Next.js App Router + Supabase SaaS početnu arhitekturu: autentikacija, Postgres podatkovni model, RLS politike, Stripe naplata i multi-tenant dizajn organizacija s konkretnim primjerima.
Feature flagovi i A/B testiranje u Next.js-u: arhitektura, alati i sigurna uvođenja (vodič za 2026.)
Praktičan vodič za implementaciju feature flagova i A/B testiranja u Next.js-u uz evaluaciju na serveru i klijentu, Edge runtime specifičnosti, analitiku i timske playbookove za rollout.
React Query vs SWR u Next.js App Routeru: Kada koristiti koji (i kako izbjeći dvostruko dohvaćanje)
Praktična usporedba za 2026. React Queryja i SWR-a unutar Next.js App Routera — modeli cacheiranja, SSR i RSC kompatibilnost, mutacije, optimistična ažuriranja, DX i provjereni obrasci za sprječavanje dvostrukog dohvaćanja.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
React obrasci u velikim aplikacijama: React Hook Form + Zod obrasci za složene proizvode
Najbolje prakse za React obrasce u velikim aplikacijama uz React Hook Form i Zod: validacija po shemi, višekratno upotrebljiva polja, asinkrone provjere, višekoračni tokovi, performanse, pristupačnost i obrasci integracije sa serverom/API-jem.
Arhitektura React komponenti za skaliranje: obrasci za održiv dizajnerski sustav
Pragmatična arhitektura React komponenti za velike React i Next.js codebaseove: kompozicija, složene (compound) i polimorfne komponente, tematiziranje, konvencije mapa, anti-uzorci i plan refaktoriranja koji vaš tim može pratiti.
Next.js i18n s App Routerom: lokalizirano rutiranje, SEO i workflowi za sadržaj (vodič za 2026.)
Implementirajte Next.js i18n u App Routeru s lokaliziranim rutiranjem, detekcijom jezika, SEO-sigurnim metapodacima i skalabilnim workflowima prevođenja za JSON, CMS ili lokalizacijske platforme.