Projekat 01 · Platforme i dizajn sistemi
Dizajn sistem s kojim web i mobilna verzija ostaju usklađene
CDS sam napravio nakon što sam na jedanaest projekata nailazio na istih pet problema. Ima 74 komponente i 678 testova, a na njemu radi i ovaj sajt.
74 / 678
glavna brojka
1 ograničenje
Iste komponente morale su raditi na Next.js-u, Viteu i React Nativeu, a da se web i mobilna verzija ne raziđu.
Priča u 5 koraka
Stack
- TypeScript
- Next.js 16
- React Native 0.87
- Vite
- styled-components
- pnpm workspaces
- Figma MCP
- vlastiti ESLint plugin
01 · Kontekst
Web i mobilna verzija proizvoda krenu identično, a dvije godine kasnije dijele samo ime.
Web i mobilna verzija proizvoda krenu identično, a dvije godine kasnije dijele samo ime. Figma je jedan izvor istine, a kod drugi, pa niko ne zna koji je tačan. Konvencije su zapisane u README-u i poštuju se dok neko ne postane zauzet. Svaki novi proizvod pravi svoj sloj komponenti od nule. A proizvod koji se prodaje pod brendovima drugih firmi ili ima dizajn tokene ili završi kao trideset kopija koje niko ne može održavati.
U CDS-u dizajn tokeni dolaze iz Figme automatski, preko MCP servera. Token teme se na svakoj platformi razrješava drugačije: na webu postaje CSS varijabla, a na nativnoj strani sentinel vrijednost koju zatim zamijeni nativni resolver. Tako razlika između platformi ostaje jedan sloj ispod mjesta poziva i nikad ne dođe do koda koji koristi komponentu. Konvencije se provjeravaju automatski: za to postoji ~2.450 linija koda, a build pada čim se neka od njih prekrši.
Sistem ima 74 komponente i 678 testova, a katalog od 81 stranice generiše se iz izvornog koda.
02 · Pet problema
Na svaki od ovih problema naišao sam na stvarnom projektu prije nego što sam počeo praviti CDS.
Na svaki od ovih problema naišao sam na stvarnom projektu prije nego što sam počeo praviti CDS.
1. Web i mobilna verzija se razilaze
Na jednoj platformi za sastanke pet ljudi je osam mjeseci gradilo svako na svoju stranu, a zajednički sloj komponenti nikad nije nastao. U kodu su na kraju bila tri UI frameworka i četiri biblioteke za padajuće menije. Na jednom marketplaceu dashboardi i mobilna aplikacija jesu dijelili biblioteku, ali je javni sajt otišao svojim putem.
Uobičajeni alati razilaženje čine besplatnim. Platform.select stavlja granu gdje god je zgodno, a uslovni importi dozvoljavaju svakom fajlu da se odvoji. Ni jedno ni drugo ne ostavlja trag i ništa ne javi grešku kad ih bude sve više.
Šta CDS radi: razliku između platformi spušta jedan sloj ispod mjesta poziva, pa kod koji koristi komponentu nikad ne mora provjeravati na kojoj je platformi. createTheme() pretvara token u string koji se razrješava po platformi, pa se ${theme.color.fg.default} svuda ponaša isto. Iznad tog sloja su zajednički recepti stilova koje primjenjuju obje platforme, a razlikuju se samo po okidaču: :hover na webu, pressed na nativnoj strani. Svaka boja ima jednu definiciju, pa se boje ne mogu razići.
Rezultat se vidi na ekranu za prijavu: poziv komponente identičan je bajt po bajt u sve tri aplikacije.
b768aa14a346 apps/web/app/LoginScreen.tsx Next.js 16, RSC
b768aa14a346 apps/spa/src/routes/LoginRoute.tsx Vite SPA
b768aa14a346 apps/mobile/App.tsx React Native 0.87LoginForm.tsx je jedan fajl od 135 linija, bez native blizanca. Sastavljen je od Box, Button, Card, Input i Text, a podjela po platformama dešava se unutar tih primitiva.
Razlikuje se omotač oko forme: na webu je centrira Box, na telefonu je smještena u SafeAreaView, a i URL API-ja je drugi. Te razlike potiču od platforme i proizvoda, a dizajn sistem ne dodaje nijednu svoju.
2. Figma i kod su dva izvora istine
Na ovo sam nailazio svuda. Na white-label proizvodu sa trideset brendova tokene je iz Figme izvozio plugin. To je bilo automatizovano, ali samo u jednom smjeru, pa su tokeni lako zaostajali za dizajnom. Na većini projekata Figmu i kod povezuje samo čovjek koji kopira hex vrijednosti.
Dizajn se mijenja po svom rasporedu, a kod po svom, pa su bez mehanizma koji ih povezuje tačni u istom trenutku samo slučajno.
Šta CDS radi: dizajn tokeni dolaze iz Figme automatski, preko MCP servera, a ručni put postoji kao rezerva. Sloj tokena je paket: core zavisi od tokens, a ui od core, pa izmjena ulazi na jednom mjestu i niko je ne mora tražiti i mijenjati po cijelom kodu.
3. Konvencije zapisane u README-u prestaju se poštovati
Na marketplaceu sa devetnaest repozitorija isti helper za red poruka bio je kopiran u sedam njih, i ništa u projektu to nije sprječavalo.
Konvenciju koja je samo zapisana ništa ne provodi, pa traje samo do prve sedmice u kojoj niko nema vremena za pažljiv pregled.
Šta CDS radi: svaka invarijanta ima svoju provjeru i build pada čim se neka prekrši.
| Skripta | Linije | Provjerava |
|---|---|---|
verify-tokens | 902 | ispravnost tokena |
verify-conventions | 1.056 | orphan-native, twin-exports, use-client, shared-platform-import |
verify-katalog | 485 | 10 strogih provjera kataloga |
To je ~2.450 linija provjera, plus ESLint plugin sa četiri pravila.
Arhitekturu na okupu drži pravilo da pattern ne smije imati .native varijantu. Provjerava ga pattern-native u verify-conventions.ts, zbog kojeg CI pada na 11 subjekata. Zbog tog pravila ekran za prijavu je ostao zajednički, i takav će ostati i kad ja prestanem paziti.
CI pokreće isti lanac umjesto da ga ponovo implementira: pnpm verify i tri prava builda, za Next, Vite i Metro. Pravi buildovi su tu zato što je next build jedino mjesto gdje se pravilo o 'use client' stvarno provjerava, a provjera koja radi samo u skripti mogla bi proći dok pravi build pada.
4. Svaki novi proizvod počinje sloj komponenti od nule
Na to sam naišao dok sam pravio tri vlastita proizvoda. Bez zajedničke biblioteke svaki bi dobio svoja dugmad, forme i modal, a tri proizvoda bi na kraju imala tri seta bugova.
Šta CDS radi: to je paket koji već koristi moja finansijska platforma, a na njemu je napravljen i ovaj sajt.
To je kompletan set komponenti: primitivi (Box, Text, Button, Input, Switch, Slider), forme (Form, Field, Select, RadioGroup, OTPInput, PasswordField), overlayi (Modal, Popover, Menu, Drawer, Tooltip, Toast), navigacija (Link, Tabs, AppBar, TabBar, Breadcrumbs) i podaci (Table, Pagination, Accordion).
Izmjerio sam šta radi sideEffects: false: ulaz koji uvozi samo Box spao je sa 253.075 B na 241.309 B. Zamka je zapisana odmah pored te postavke. @cds/tokens se ne smije označiti kao false, jer se tada izbaci reset.css i raspored se raspadne bez ijedne greške. Nijedan alat u lancu tu grešku ne prijavljuje, pa bilješka stoji tamo gdje će sljedeći čovjek pogledati.
5. Naivno postavljena tema za više brendova umnožava varijante
Jedan građevinski proizvod prodaje se pod više od trideset brendova. Da svaka komponenta za svaki brend dobije zasebnu verziju, njegovih sto komponenti pretvorilo bi se u tri hiljade varijanti koje treba ažurirati.
Šta CDS radi: komponente se na boje pozivaju samo preko imena tokena. Brend se dodaje novim setom vrijednosti tokena, a nijedna komponenta se ne mijenja. Taj pristup se već dokazao na građevinskom proizvodu, a u CDS-u je dio arhitekture od prvog commita.
03 · Princip
38 od 74 komponente ima native blizanca, što zvuči kao da se pola sistema razišlo.
div u View ili input u TextInput, i ne rade ništa više.38 od 74 komponente ima native blizanca, što zvuči kao da se pola sistema razišlo. U stvari su blizanci gotovo svi u jednom sloju:
| Sloj | Blizanci |
|---|---|
primitives/ | 27 |
overlay/ | 8 |
form/ | 2 |
navigation/ | 1 |
patterns/ | 0 od 26 |
33 od 38 blizanaca ne uvozi nijednu drugu komponentu. To su listovi koji prevode jedan element platforme, div u View ili input u TextInput, i ne rade ništa više. Podjela je na dnu stabla, i zato ništa iznad nje ne mora znati za nju.
Blizanac je dozvoljen samo iz tri razloga, a dva od njih uopšte nisu vezana za elemente.
Drugačiji element. Ovo važi za 33 od 38 blizanaca. Promjena je mehanička i ograničena, a komponente iznad nje je ne vide.
Drugačiji događaj. Tooltip.native postoji zato što na ekranu na dodir nema hovera, pa je okidač dugi pritisak, i to namjerno bez odgode. Na webu delayShow sprječava treperenje dok kursor prelazi preko reda ikona. Dugi pritisak već pokazuje namjeru, pa bi zbog odgode tooltip samo djelovao pokvareno.
Drugačiji prozor. NativeSelect.native postoji zato što sistemski picker mora biti iznad tastature, a do zasebnog native prozora može se doći samo kroz RN Modal. Zajednički Portal predaje stablo hostu, pa bi lista ostala ispod tastature. Ta greška se vidi samo na uređaju, i to samo unutar forme.
Preostala tri blizanca uvoze tačno po jednu komponentu i ne mijenjaju sliku.
Nijedan pattern nema blizanca, i to se provjerava: zbog pattern-native u sloju provjera CI pada na 11 subjekata čim se blizanac pojavi. Modal pokazuje da i težak slučaj može ostati zajednički, s portalom, upravljanjem fokusom i mjerenjem, a LoginForm da to može i cijeli ekran.
Kad je patternu trebala podjela, ostavio sam pattern zajedničkim, izdvojio primitiv čija je jedina razlika bio element i blizanca dao tom primitivu. To se pod pritiskom desilo četiri puta, i zato to smatram principom. Među izdvojenim primitivima su PositionedLayer, DialogActionsRow i MenuSeparator.
04 · Brojke
| Komponente | 74 |
| Sa native blizancima | 38, od kojih je otprilike polovina doslovno isti fajl |
| Testovi | 678 u 62 suite-a |
| Stranice kataloga | 81, tabele propsa generisane iz koda |
| Alati za provjeru | ~2.450 linija plus 4 ESLint pravila |
| Bundle, ulaz samo sa Box | 253.075 B → 241.309 B |
| Trajanje | 6 mjeseci, u toku |
| Koriste ga | ovaj sajt i moja finansijska platforma |
05 · Pregled komponenti
Pregledao sam svih 75 komponenti, koliko ih je sistem tada imao, i našao 188 problema.
accessibilityLabel na čvoru bez uloge ne čita nijedna platforma.Omit<BoxProps, …> ostavlja id, a11yRole, accessibilityLabel i pointerEvents u ugovoru, ali grana koja ih ne destrukturira prosljeđuje ih u ...box, a odatle u style helper koji vraća samo $ propse.Pregledao sam svih 75 komponenti, koliko ih je sistem tada imao, i našao 188 problema. Svi se svode na tri osnovna uzroka, a nijedan alat u lancu ih ne uhvati: typecheck, ESLint i build prolaze.
Labela na elementu bez uloge. accessibilityLabel na čvoru bez uloge ne čita nijedna platforma. Na webu aria-label na golom div ne radi ništa, jer ARIA specifikacija ne dozvoljava da generička uloga dobije ime koje zada autor. Na nativnoj strani se accessibilityLabel bez accessible nikad ni ne pročita. Ovu grešku imalo je devet komponenti, a ispravan obrazac je već postojao u istom paketu, gdje Icon svoju labelu uvijek uparuje s role="img".
Naslijeđeni prop koji nikad ne stigne do elementa. Omit<BoxProps, …> ostavlja id, a11yRole, accessibilityLabel i pointerEvents u ugovoru, ali grana koja ih ne destrukturira prosljeđuje ih u ...box, a odatle u style helper koji vraća samo $ propse. Ništa se ne buni, jer je izostavljen prop samo undefined, a to je validna vrijednost. Četiri srodne komponente povezale su tri različita podskupa tih propsa: jedna sva četiri, dvije po dva, a jedna nijedan.
Komentar koji opisuje raniju verziju sistema. Bilješka opisuje neko ograničenje, ograničenje se kasnije ukloni, a bilješka ostane. To je najpodmuklija vrsta greške, jer čitalac komentaru vjeruje i nikad ne pokuša ono što je već mjesecima moguće. Još gora varijanta je JSDoc blok pomaknut za jedan prop, pa jedan prop ima dva opisa, a njegov susjed nijedan. Taj tekst se generiše u katalog, gdje postaje javna dokumentacija.
Važnije od broja 188 je razumjeti zašto su tri alata šutila, jer od toga zavisi hoće li sljedeća greška iste vrste biti uhvaćena. Dva od tri uzroka sada pokrivaju pravila provjere, pa njihovo otkrivanje više ne zavisi od pregleda.
Da to radim ponovo, pregled bih uradio kad je sistem imao 30 komponenti, mnogo prije nego što ih je bilo 75. Ista tri uzroka postojala su i tada, a ispravka bi bila za red veličine jeftinija. Svih 188 problema je ispravljeno, a pokrivenost za aria-label i tabindex još je otvorena.
Povezane usluge
Usluge koje se oslanjaju na ovaj projekat.
- Mobilne aplikacijeReact Native razvojNaš dizajn sistem radi na tri platforme. Kad smo svoju marketing stranicu prenijeli na telefon, 91% koda koji telefon izvršava došlo je s weba bez izmjena.
- Web razvojArhitektura React SPAVite SPA je jedan od tri targeta našeg dizajn sistema, uz Next.js i React Native. Poziv koji iscrtava ekran za prijavu isti je do posljednjeg bajta u svakoj od tih aplikacija.
- Web razvojSaaS MVPNaš dizajn sistem, sa 74 komponente i 678 testova. Na njemu radi ovaj sajt, a i jedna naša finansijska platforma.
- Mobilne aplikacijePort sa weba na mobilniEkran za prijavu u našem dizajn sistemu poziva komponente kodom koji je do bajta identičan u sve tri aplikacije: Next.js, Vite SPA i React Native. Od platforme do platforme razlikuje se samo omotač oko njega.
- Cloud i DevOpsCI/CD i standardi kodaNaš cross-platform dizajn sistem ima oko 2.450 linija provjera zbog kojih build pada. CI pokreće i stvarne buildove za Next, Vite i Metro, jer jedno od pravila zaista provjerava tek next build.
- Cloud i DevOpsAutomatizovano i end-to-end testiranje678 testova u 62 suitea u našem cross-platform dizajn sistemu, za web, SPA i native.
- AIAI u vašem inženjerskom timuU našem dizajn sistemu build padne kad generisana izmjena prekrši neku od konvencija, jer ih provjerava oko 2.450 linija koda.
- Dizajn sistemiDizajn sistem i biblioteka komponentiNaš cross-platform dizajn sistem: 74 komponente, 678 testova i katalog od 81 stranice generisan iz koda. Forma za prijavu u njemu je jedan fajl koji bez izmjena koriste aplikacije u Next.js-u, Viteu i React Nativeu.
- Dizajn sistemiDizajn tokeni i teme za više brendovaU našem cross-platform dizajn sistemu token teme na webu postaje CSS varijabla, a na telefonu vrijednost koju upisuje native resolver. Svaka boja definisana je jednom, za sve platforme.
- Dizajn sistemiFigma u kodNaš sadašnji dizajn sistem preuzima palete, skale i vrijednosti teme iz Figme automatski, preko MCP servera.
Povezani tekstovi
Tekstovi nastali iz ovog projekta.
- 26 SEP 2026 · 5 minNaš dizajn sistem upakovan kao skill ubrzao je AI agenta barem dvostrukoAI agent je isti dizajn napravio za 31,7 sati s našim dizajn sistemom kao bibliotekom, a zatim za 2,5 sata s istim sistemom kao skillom. Dio razlike objašnjava noviji model.
- 9 SEP 2026 · 10 minTestiranje dizajn sistema na pravoj stranici: 7 komponenti i 8 nedostatakaStranici je trebalo sedam od 74 komponente i četiri izuzetka u jednom fajlu. Na telefonu je 91% koda koji se izvršava ostalo neizmijenjeno, a četiri buga prošla su sve provjere.
- 5 SEP 2026 · 6 minStranica s napola izgrađenim dizajn sistemom bila je veća od čistog HTML-aIstu stranicu izmjerio sam u tri verzije: ručno pisani styled-components imali su 32.149 znakova, čisti HTML 23.139, a naš dizajn sistem 18.040.
- 5 AUG 2026 · 7 minGdje sam u našem dizajn sistemu razdvojio web i React Native kodOd 74 komponente u našem dizajn sistemu 38 ima React Native blizanca, od toga 27 primitiva i nijedan pattern. Svaki blizanac postoji zbog drugačijeg elementa, događaja ili prozora.
- 8 JUL 2026 · 4 minTri vrste grešaka u dizajn sistemu koje TypeScript i ESLint propusteU 75 komponenti našao sam 188 propusta, a svi su se sveli na tri uzroka: labele na čvorovima bez role, propove koje komponenta prihvati pa odbaci i zastarjele komentare.
Projekat 01
Radite na sličnom problemu? Odgovaramo u roku od jednog radnog dana.
Prvi poziv traje 30 minuta i ni na šta vas ne obavezuje.