Բոլոր դասերը
FrontendՄիջին12 րոպե ընթերցումԻմ ստեկում

CSS և design system-ներ

Design token-ներ, սանդղակներ, թեմաներ և ձեր stack-ին համապատասխան ոճավորման գործիքը։

  • Design tokens
  • CSS Modules
  • styled-components
  • Tailwind
  • Responsive
Քայլ 1/4
Դիզայն համակարգ չկա. ամեն կոճակ ընտրել է իր hex կոդն ու անկյունների radius-ը։ Դիզայները հաշվում է «բրենդային կապույտի» 47 երանգ։

30 վայրկյանում

Design system-ը որոշումների ընդհանուր հավաքածու է (գույներ, spacing, տառատեսակ, radius-ներ, անիմացիա), որը պահվում է որպես անվանված token-ներ և օգտագործվում է յուրաքանչյուր կոմպոնենտի կողմից։ Հարյուր տեղում #1a1a1a կամ 17px գրելու փոխարեն գրում եք var(--color-text) և var(--space-4)։ Արդյունքը UI է, որը համահունչ տեսք ունի, հեշտությամբ է վերաթեմավորվում և մնում է սպասարկելի, երբ թիմն ու կոդի բազան աճում են։

Ինչու եմ օգտագործում

  • Design token-ներ օգտագործում եմ ամեն նախագծում, որովհետև դրանք հարյուրավոր փոքր վիզուալ ընտրությունները վերածում են մի քանի անվանված որոշումների, որոնք կարող են կարդալ թե՛ դիզայները, թե՛ ծրագրավորողը։
  • Տեսել եմ, թե ինչ է լինում առանց դրանց. յուրաքանչյուր նախագծի համար override ֆայլեր՝ հարյուրավոր կամ հազարավոր տող !important-ով, որտեղ ոչ ոք չի համարձակվում գույնը փոխել։
  • Ոճավորման գործիքի ընտրությունն ինձ համար կարևոր է, որովհետև հենց այն է որոշում՝ կոմպոնենտը կարո՞ղ է render լինել սերվերում, որքան JavaScript է գնում բրաուզեր և որքան ցավոտ կլինի redesign-ը։
  • Հստակ responsive ռազմավարությունը (breakpoint-ների ֆիքսված ցուցակ գումարած container query-ներ) այն է, ինչ layout-ին թույլ է տալիս դիմանալ իրական կոնտենտին, իրական լեզուներին և իրական սարքերին։

Ինչպես է աշխատում

01Գաղափարը. որոշումներ, ոչ թե արժեքներ

Design system-ը պատկերացրեք որպես բրենդի կանոնագիրք։ #0f62fe-ի նման «հում» արժեքը ոչինչ չի ասում, իսկ --color-accent-ի նման token-ը ասում է, թե ինչի համար է այն։ Token-ները սովորաբար լինում են երկու շերտով։ Primitive token-ները նկարագրում են պալիտրան (--blue-600, --gray-100)։ Semantic token-ները նկարագրում են նպատակը (--color-accent, --color-surface, --color-text-muted) և հղում են primitive-ներին։ Կոմպոնենտներն օգտագործում են միայն semantic token-ներ։ Հենց այս մեկ կանոնն է թեմավորումն ու redesign-ները էժանացնում. փոխում եք, թե ինչին է հղում --color-surface-ը, և յուրաքանչյուր card, modal և input հետևում է դրան։ Token-ները միայն գույնը չեն ընդգրկում. spacing-ը, տառաչափերը, line height-ները, radius-ները, ստվերները, z-index շերտերը, տևողություններն ու easing-ները նույնպես նույն ֆայլում են։

css
:root {
  /* primitives */
  --gray-50: #f7f7f8;
  --gray-900: #16161a;
  --blue-600: #2952e3;
 
  /* semantic: components use only these */
  --color-bg: var(--gray-50);
  --color-text: var(--gray-900);
  --color-accent: var(--blue-600);
  --radius-md: 0.5rem;
  --duration-fast: 150ms;
}

02Spacing-ի և տառատեսակի սանդղակներ

Սանդղակը թույլատրված չափերի կարճ, ֆիքսված ցուցակ է։ Այստեղ 13px, այնտեղ 15px գրելու փոխարեն ընտրում եք քայլեր, օրինակ՝ 4, 8, 12, 16, 24, 32, 48, 64 spacing-ի համար, և modular ratio (օրինակ՝ 1.2 կամ 1.25) տառաչափերի համար։ Քիչ տարբերակները նշանակում են ավելի արագ որոշումներ և տեսանելի ռիթմ։ Fluid type-ը օգտագործում է clamp(), որպեսզի վերնագրերը սահուն մեծանան նվազագույնի և առավելագույնի միջև՝ առանց breakpoint-ների. clamp(2rem, 1.2rem + 3vw, 3.5rem)։ Տառաչափերի համար օգտագործեք rem, որպեսզի օգտատիրոջ բրաուզերի տառաչափի կարգավորումը հարգվի, իսկ line-height-ը պահեք առանց միավորի (1.5՝ տեքստի համար, 1.1-ից 1.2՝ մեծ վերնագրերի համար)։

  • Spacing. 4px կամ 8px բազային grid՝ --space-1-ից --space-10 անուններով։
  • Տառատեսակ. 6-ից 8 քայլը բավարար է պրոդուկտների մեծ մասի համար։
  • Fluid. clamp(min, preferred, max) վերնագրերի և section-ների padding-ի համար։
  • Երբեք թույլ մի տվեք կոմպոնենտին հորինել չափ, որը սանդղակում չկա։

03Թեմավորում. light, dark և բրենդներ

Քանի որ կոմպոնենտները կարդում են semantic token-ներ, թեման պարզապես նույն անունների համար արժեքների այլ հավաքածու է։ Dark mode-ը կարող է հետևել օպերացիոն համակարգին prefers-color-scheme-ի միջոցով կամ օգտատիրոջ ընտրությանը, որը պահվում է html էլեմենտի data-theme ատրիբուտում։ Ժամանակակից CSS-ն ունի նաև light-dark() ֆունկցիան, որը color-scheme property-ից ելնելով ընտրում է երկու արժեքից մեկը, այնպես որ շատ token-ներ կարելի է գրել մեկ անգամ։ White-label պրոդուկտների համար յուրաքանչյուր բրենդ token-ների ևս մեկ հավաքածու է, որը բեռնվում է root-ում։ Թեմայի ատրիբուտը դրեք սերվերում կամ փոքրիկ inline script-ով՝ մինչև paint-ը, այլապես օգտատերերը կտեսնեն սխալ թեմայի «բռնկում»։

css
:root {
  color-scheme: light dark;
  --color-bg: light-dark(#ffffff, #121214);
  --color-text: light-dark(#16161a, #ececf1);
}
 
/* explicit user choice wins over the OS setting */
[data-theme="dark"] { color-scheme: dark; }
[data-theme="light"] { color-scheme: light; }

04CSS Modules, styled-components, թե՞ Tailwind

Երեքն էլ կարող են լավ design system կառուցել. տարբերվում են նրանով, թե որտեղ են ապրում ոճերը և ինչ է աշխատում runtime-ում։ CSS Modules-ը սովորական CSS ֆայլեր են՝ լոկալ scope ունեցող class անուններով, որոնք կոմպիլացվում են build-ի ժամանակ, զրո runtime արժեքով և React Server Components-ի լիարժեք աջակցությամբ։ styled-components-ը CSS-in-JS է. ոճերն ապրում են կոմպոնենտի ներսում և կարող են արձագանքել props-ին, բայց ոճերը գեներացվում են բրաուզերում և հենվում են React context-ի վրա, ուստի Next.js App Router-ում դրանց պետք են client կոմպոնենտներ և style registry՝ SSR-ի համար։ Նախագիծը 2025-ից maintenance mode-ում է և լավ default չէ նոր RSC նախագծերի համար։ Tailwind-ը utility-first է. markup-ում հավաքում եք px-4 text-sm-ի նման class-ներ։ 4-րդ տարբերակը token-ները սահմանում է CSS-ում @theme-ով, գեներացնում է միայն այն class-ները, որոնք օգտագործում եք, և հիանալի աշխատում է server կոմպոնենտների հետ։

  • CSS Modules. իսկական CSS, զրո runtime, RSC-ի համար անվտանգ. անունները ինքներդ եք տալիս։
  • styled-components. դինամիկ props և co-location. runtime արժեք, RSC-ում՝ միայն client։
  • Tailwind. շատ արագ է կառուցել, փոքրիկ CSS output, RSC-ի համար անվտանգ. class-ների երկար ցուցակները կարգապահություն են պահանջում։
  • Zero-runtime CSS-in-JS-ը (vanilla-extract, Panda, StyleX) գտնվում է դրանց միջև։

05Կոմպոնենտային գրադարաններ

Ձեր token ֆայլը հիմքն է, իսկ կոմպոնենտային գրադարանը դրա վրա կառուցված տունը։ Սկսեք ձանձրալի, բայց ամենաշատ օգտագործվող մասերից՝ Button, Input, Select, Checkbox, Modal, Toast, Card, Stack և Grid layout primitive-ներ։ Յուրաքանչյուր կոմպոնենտ պետք է ունենա փոքր, տիպավորված API (variant, size, disabled)՝ կամայական ոճեր ընդունելու փոխարեն, որպեսզի համակարգը մնա համահունչ։ Բարդ ինտերակտիվ widget-ների (dialog-ներ, menu-ներ, combobox-ներ) համար կառուցեք headless, accessible primitive-ների վրա, ինչպիսիք են Radix-ը, React Aria-ն կամ Ark UI-ը, այլ ոչ թե ինքներդ գրեք focus trap-եր և ստեղնաշարի մշակում։ Յուրաքանչյուր կոմպոնենտ փաստաթղթավորեք իրական օրինակներով, իսկ ընդհանուր փաթեթներին տվեք semantic versioning, որպեսզի button-ի breaking change-ը անակնկալ չլինի այն օգտագործող տասը հավելվածների համար։

tsx
// Button/index.tsx - a small, closed API
import s from "./style.module.css";
 
type Props = {
  variant?: "primary" | "ghost";
  size?: "sm" | "md";
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
 
export function Button({ variant = "primary", size = "md", className, ...rest }: Props) {
  const cls = [s.button, s[variant], s[size], className].filter(Boolean).join(" ");
  return <button className={cls} {...rest} />;
}

06Responsive ռազմավարություն և container query-ներ

Ընտրեք breakpoint-ների մեկ ցուցակ, պահեք այն մեկ տեղում և օգտագործեք ամենուր։ Mobile-first (min-width), թե desktop-first (max-width) եք գնում, ավելի քիչ է կարևոր, քան հետևողական լինելը։ Media query-ները պատասխանում են «որքա՞ն լայն է էկրանը» հարցին։ Container query-ները կոմպոնենտների համար պատասխանում են ավելի լավ հարցի՝ «որքա՞ն լայն է այն տուփը, որի մեջ ես եմ»։ Ապրանքի card-ը կարող է իրար տակ դասավորությունից անցնել հորիզոնականի, երբ իր container-ը բավականաչափ լայն է՝ անկախ նրանից, sidebar-ում է այն, թե ամբողջ լայնությամբ grid-ում։ Container query-ները և cqi միավորը աջակցվում են բոլոր ժամանակակից բրաուզերներում։ Օգտագործեք նաև intrinsic layout-ներ. grid-ը repeat(auto-fill, minmax(16rem, 1fr))-ով հաճախ ընդհանրապես վերացնում է breakpoint-ների կարիքը։

css
.cardWrap {
  container-type: inline-size;
}
 
.card {
  display: grid;
  gap: var(--space-3);
}
 
@container (min-width: 28rem) {
  .card { grid-template-columns: 10rem 1fr; }
}

07Խորհուրդներ production-ի համար

Համակարգը պաշտպանեք գործիքներով, ոչ թե հիշողությամբ։ Stylelint-ը կարող է արգելել «հում» hex գույները և px արժեքները token ֆայլից դուրս։ Cascade layer-ները (@layer reset, tokens, components, utilities) specificity-ն դարձնում են կանխատեսելի, այնպես որ երբեք !important պետք չի լինի։ Token-ների փոփոխությունները պահեք additive. token-ի վերանվանումը կամ ջնջումը breaking change է, ուստի ներմուծեք նորը, միգրացիա արեք, հետո հեռացրեք հինը։ Կոմպոնենտները թեստավորեք ձեր իրական breakpoint-ներում և երկար թարգմանված տողերով, որովհետև գերմաներեն, ռուսերեն կամ հայերեն տեքստը կոտրում է layout-ներ, որոնք անգլերենով լավ են երևում։ Վերջապես, ստուգեք կոնտրաստը տեքստի և ֆոնի token-ների յուրաքանչյուր զույգի համար երկու թեմաներում էլ։

  • Lint կանոն. ոչ մի hex, ոչ մի px token-ներից դուրս։
  • !important-ի փոխարեն օգտագործեք @layer։
  • Նախ ավելացրեք token-ները, միգրացիա արեք, հետո ջնջեք։
  • Թեստավորեք ամենաերկար locale-ով և յուրաքանչյուր breakpoint-ում։

Օգտագործեք, երբ

  • Ցանկացած պրոդուկտ, որն ունի մի քանի էկրանից ավելի կամ մեկից ավելի ծրագրավորող։
  • Երբ մեկ կոդի բազայից ձեզ պետք է dark mode, white-labelling կամ մի քանի բրենդ։
  • Երբ մի քանի հավելված պետք է նույն տեսքն ու վարքն ունենան և ընդհանուր կոմպոնենտներ օգտագործեն։
  • Redesign-ից առաջ, որպեսզի հաջորդը լինի token-ների փոփոխություն, ոչ թե ամբողջական վերագրում։

Մի՛ օգտագործեք, երբ

  • Մեկ էջանոց landing, որը կապրի մի քանի շաբաթ. մի քանի CSS փոփոխականը բավական է։
  • Մի կառուցեք ամբողջական կոմպոնենտային գրադարան, քանի դեռ պրոդուկտի UI-ը չի կայունացել. կոմպոնենտներն առանձնացրեք այն բանից հետո, երբ տեսել եք, որ դրանք կրկնվում են։

Այլընտրանքներ

  • Tailwind CSS

    Utility class-ներ՝ @theme-ում սահմանված token-ներով. արագ է կառուցել, RSC-friendly է, markup-ը՝ opinionated։

  • CSS Modules

    Scope ունեցող սովորական CSS՝ զրո runtime-ով. իմ default-ը server-first Next.js նախագծերի համար։

  • styled-components / Emotion

    Runtime CSS-in-JS. հարմար props-ի վրա հիմնված ոճավորում, բայց React Server Components-ի դեպքում՝ միայն client։

  • vanilla-extract / Panda CSS / StyleX

    Type-safe ոճեր TypeScript-ով, որոնք build-ի ժամանակ կոմպիլացվում են ստատիկ CSS-ի։

Լավագույն պրակտիկաներ

  1. Token-ների երկու շերտ. primitive-ներ՝ պալիտրայի համար, semantic token-ներ՝ կոմպոնենտների համար։
  2. Կոմպոնենտներն օգտագործում են միայն semantic token-ներ, երբեք «հում» արժեքներ։
  3. Պահեք breakpoint-ների մեկ ցուցակ և spacing-ի մեկ սանդղակ մեկ ֆայլում։
  4. Նախընտրեք container query-ներ այն կոմպոնենտների համար, որոնք հայտնվում են տարբեր layout-ներում։
  5. Բարդ widget-ները կառուցեք accessible headless primitive-ների վրա։
  6. Token-ների փոփոխությունները դարձրեք additive և version տվեք ընդհանուր փաթեթներին։
  7. Ստուգեք գույների կոնտրաստը տեքստի և ֆոնի յուրաքանչյուր զույգի համար ամեն թեմայում։

Տարածված սխալներ

  • Hex և px արժեքներ hard-code անել կոմպոնենտներում, ինչը թեմավորումն անհնար է դարձնում։
  • Specificity-ի դեմ պայքարել !important-ով՝ cascade layer-ներ կամ scope ունեցող ոճեր օգտագործելու փոխարեն։
  • Server components-first հավելվածի համար ընտրել runtime CSS-in-JS գրադարան, իսկ հետո ամեն ինչ նշել որպես client կոմպոնենտ։
  • Յուրաքանչյուր կոմպոնենտի համար breakpoint-ներ հորինել, այնպես որ layout-ը «ցատկում» է տասնյակ տարբեր լայնություններում։
  • Կոմպոնենտներին ամեն ինչի համար ոճի «escape hatch» տալ, ինչը դանդաղ ոչնչացնում է համահունչությունը։

Խորը վերլուծություն

08Cascade layers՝ specificity-ի ճարտարապետություն

Cascade layers-ը (@layer) որոշում է, թե ոճերի որ խումբն է հաղթում, դեռ մինչև specificity-ի համեմատությունը։ Դուք մեկ անգամ հայտարարում եք հերթականությունը, օրինակ՝ reset, vendor, tokens, base, components, utilities, և ավելի ուշ շերտի ցանկացած կանոն հաղթում է ավելի վաղ շերտի ցանկացած կանոնին՝ անկախ նրանից, թե որքան սպեցիֆիկ է դրա selector-ը։ Սա վերջ է դնում ավելի երկար selector-ների և !important-ի մրցավազքին։ Երեք մանրուք հաճախ է մարդկանց շփոթեցնում։ Որևէ շերտից դուրս գտնվող ոճերը հաղթում են բոլոր շերտավորված ոճերին, ուստի այն CSS-ը, որը դուք չեք վերահսկում, պետք է ներմուծել ցածր շերտ՝ @import url(...) layer(vendor)-ով, այլապես այն լուռ կհաղթի։ !important-ը շրջում է հերթականությունը. վաղ շերտի important հայտարարությունը հաղթում է ավելի ուշ շերտի important հայտարարությանը, ինչը թույլ է տալիս reset-ին պաշտպանել accessibility-ի կանոնները։ Եվ հերթականությունը ֆիքսվում է այն պահին, երբ շերտի անունն առաջին անգամ հայտնվում է, ուստի ամբողջ ցուցակը հայտարարեք մուտքային stylesheet-ի հենց վերևում՝ ցանկացած import-ից առաջ։

css
@layer reset, vendor, tokens, base, components, utilities;
 
@import url("datepicker.css") layer(vendor);
 
@layer components {
  .button { background: var(--color-accent); }
  .card .button.primary { background: var(--color-accent-strong); }
}
 
@layer utilities {
  /* wins over both rules above, despite lower specificity */
  .bg-surface { background: var(--color-surface); }
}

09Գույն, որը կարելի է հաշվարկել՝ OKLCH, color-mix() և relative colours

Hex-ն ու HSL-ը վատ են հարմարված սանդղակներ կառուցելու համար։ Նույն lightness արժեքով երկու HSL գույն կարող են պայծառությամբ շատ տարբեր տեսք ունենալ, ուստի գեներացված երանգները անկանխատեսելիորեն չեն անցնում contrast-ի ստուգումը։ OKLCH-ը գույնը նկարագրում է որպես ընկալվող lightness, chroma (գունագեղություն) և hue, ուստի lightness-ը 0.95-ից մինչև 0.25 քայլերով իջեցնելը տալիս է հավասարաչափ տեսք ունեցող սանդղակ և հեշտացնում contrast-ի մասին դատողությունը։ Ժամանակակից CSS-ը կարող է գույներ ստանալ նաև հենց բրաուզերում։ color-mix(in oklch, var(--brand), black 15%)-ը ցանկացած բրենդային գույնից hover երանգ է ստեղծում, իսկ relative colour syntax-ը՝ oklch(from var(--brand) l c h), թույլ է տալիս փոխել մեկ ալիք և պահել մնացածը։ Այս ամենն աշխատում է հիմնական բրաուզերների ընթացիկ տարբերակներում։ Գործնական օգուտը white-label-ն է. tenant-ը տալիս է մեկ կամ երկու բրենդային գույն, և համակարգը հաշվարկում է hover, pressed, subtle background և border վիճակները։ Հաշվարկված գույները մեկնարկային կետ են, ոչ թե երաշխիք, ուստի միևնույն է ստուգեք տեքստի և ֆոնի յուրաքանչյուր զույգը WCAG contrast-ով։

css
:root {
  --brand: oklch(0.58 0.17 255);
 
  --color-accent: var(--brand);
  --color-accent-hover: color-mix(in oklch, var(--brand), black 15%);
  --color-accent-subtle: oklch(from var(--brand) 0.95 calc(c * 0.3) h);
  --color-accent-border: oklch(from var(--brand) calc(l + 0.15) c h);
  --color-on-accent: oklch(0.99 0 0);
}
 
[data-brand="b"] {
  --brand: oklch(0.62 0.19 25); /* the only value a new brand changes */
}

10Token-ները որպես pipeline, ոչ թե ֆայլ

Մեկ հավելվածում մեկ tokens.css ֆայլը բավական է։ Մի քանի վեբ հավելվածի, native մոբայլ հավելվածի և դիզայն գործիքի դեպքում token-ները դառնում են արտադրանք՝ իր սպառողներով։ Ճշմարտության աղբյուրն այդ դեպքում JSON ֆայլ է W3C Design Tokens Community Group-ի (DTCG) ֆորմատով, որը հրապարակել է իր առաջին կայուն տարբերակը՝ 2025.10, և որը հասկանում են Figma plugin-ները և build գործիքները։ Style Dictionary-ի նման transformer-ը այդ աղբյուրը վերածում է CSS custom properties-ի, autocompletion-ի համար typed TypeScript օբյեկտի և մոբայլի համար Swift կամ Kotlin հաստատունների։ Վերաբերվեք package-ին ինչպես ցանկացած հանրային API-ի՝ semantic versioning, changelog և deprecation-ներ, որոնք հին անունը պահում են որպես alias մեկ major տարբերակի ընթացքում, մինչ lint կանոնը զգուշացնում է դրա մասին։ Յուրաքանչյուր token փոփոխության ժամանակ գործարկեք screenshot թեստեր (օրինակ՝ Storybook visual diff ծառայությամբ), որովհետև մեկ արժեքը կարող է միանգամից տեղաշարժել հարյուրավոր կոմպոնենտներ։

json
{
  "color": {
    "blue-600": {
      "$type": "color",
      "$value": { "colorSpace": "srgb", "components": [0.145, 0.388, 0.922], "hex": "#2563eb" }
    },
    "accent": { "$type": "color", "$value": "{color.blue-600}" }
  },
  "space": {
    "4": { "$type": "dimension", "$value": { "value": 1, "unit": "rem" } }
  }
}

11CSS-ի runtime արժեքը մեծ մասշտաբում

CSS-ի արտադրողականությունը հազվադեպ է կարևոր, քանի դեռ էջում հազարավոր էլեմենտներ չկան. այդ դեպքում մի քանի բան իրական ժամանակ է խլում։ Style recalculation. :root-ի վրա custom property փոխելը, օրինակ՝ թեմայի փոխարկման ժամանակ, անվավեր է դարձնում ոճերը յուրաքանչյուր էլեմենտի համար, որը ժառանգում է այն։ Մեկ կտտոցի համար մեկ անգամը նորմալ է, բայց root փոփոխականն ամեն frame անիմացիա անելը՝ ոչ, ուստի անիմացվող փոփոխականները սահմանափակեք այն էլեմենտով, որն օգտագործում է դրանք։ Գրանցեք անիմացվող փոփոխականները @property-ով և inherits: false-ով. դա նրանց տալիս է իրական տիպ, թույլ է տալիս սահուն interpolate լինել և կանգնեցնում է փոփոխության տարածումը ծառի միջով։ Չօգտագործված CSS. գլոբալ stylesheet-ը միայն մեծանում է, ուստի նախընտրեք կոմպոնենտային CSS (CSS Modules կամ Tailwind, որը գեներացնում է միայն իր գտած class-ները) և ստուգեք DevTools-ի Coverage վահանակը։ Էկրանից դուրս աշխատանք. content-visibility: auto-ն contain-intrinsic-size-ի հետ թույլ է տալիս բրաուզերին բաց թողնել layout-ն ու paint-ը fold-ից ներքև գտնվող երկար բաժինների համար։ Selector matching-ը հազվադեպ է խցան. նախքան selector-ները վերաշարադրելը չափեք Performance վահանակի selector stats-ով։

css
@property --progress {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}
 
.meter {
  background: linear-gradient(90deg, var(--color-accent) var(--progress), transparent 0);
  transition: --progress 300ms ease-out;
}
 
.long-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 800px;
}

Իրական իրավիճակներ

01

White-label հաճախորդին երկու շաբաթում պետք է իր բրենդը

Խնդիր

Արտադրանքը վաճառվում է երկրորդ հաճախորդին, որն ուզում է իր գույները, տառատեսակը և անկյունների radius-ը։ Կոմպոնենտներում token-ներն ու hard-coded արժեքները խառն են, ուստի ոչ ոք չգիտի, թե որքան բան կկոտրվի։

Ինչպես լուծել

Սկսեք աուդիտից՝ կոմպոնենտների ֆայլերում փնտրեք hex, rgb() և px արժեքներ և կազմեք ցուցակ. այն ամենը, ինչ պետք է փոխվի ըստ բրենդի, դառնում է semantic token։ Հետո ավելացրեք բրենդի mapping՝ [data-brand] բլոկ, որը semantic token-ները վերակապում է նոր primitive-ներին, գումարած font-family և radius token-ներ։ Hover, pressed և subtle վիճակները ստացեք color-mix()-ով կամ relative colour syntax-ով, որպեսզի հաճախորդը տրամադրի ընդամենը երկու-երեք գույն։ Գործարկեք ավտոմատ contrast ստուգում տեքստի և ֆոնի յուրաքանչյուր զույգի համար՝ երկու բրենդների համար էլ, երկու թեմաներում։ Վերջում միացրեք Stylelint կանոն, օրինակ՝ color-no-hex, կոմպոնենտների պանակների համար, որպեսզի նոր hard-coded արժեքները ձախողեն CI-ը։ Կոմպոնենտների տրամաբանությունը չի փոխվում, փոխվում են միայն արժեքները։

02

Third-party widget-ների ոճերը անընդհատ գերակայում են դիզայն համակարգին

Խնդիր

Date picker-ը և chat widget-ը բերում են իրենց գլոբալ CSS-ը։ Նրանց որոշ selector-ներ հաղթում են ձեր կոմպոնենտների ոճերին, և թիմը սկսել է !important ավելացնել՝ պատասխան պայքարելու համար։

Ինչպես լուծել

Տեղափոխեք vendor CSS-ը ցածր cascade layer։ Հայտարարեք հերթականությունը մուտքային stylesheet-ի վերևում, օրինակ՝ @layer reset, vendor, base, components, utilities, և ներմուծեք widget-ի CSS-ը layer(vendor)-ով։ Հիմա կոմպոնենտի ցանկացած կանոն հաղթում է՝ անկախ specificity-ից։ Հեռացրեք !important կարկատանները մեկ առ մեկ՝ յուրաքանչյուրը ստուգելով visual regression գործարկմամբ։ Եթե widget-ը runtime-ում style tag է ներարկում, որը չեք կարող շերտի մեջ դնել, փաթաթեք այն կոնտեյների մեջ և թիրախավորեք components շերտից @scope-ով, կամ մեկուսացրեք shadow root-ում։ Գրեք շերտերի հերթականությունը contribution guide-ում, որպեսզի նոր կոդը հայտնվի ճիշտ շերտում։

03

Dark mode-ն անցնում է դիզայն review-ն, բայց ձախողում accessibility աուդիտը

Խնդիր

Մուգ թեման թիմին լավ է թվում, բայց աուդիտը հայտնաբերում է muted տեքստ և placeholder տեքստ 4.5:1-ից ցածր, ինչպես նաև focus ring-եր, որոնք անհետանում են մուգ մակերեսների վրա։

Ինչպես լուծել

Contrast-ը դարձրեք թեստ, ոչ թե գնահատական։ Արտահանեք յուրաքանչյուր semantic տեքստի, պատկերակի և border-ի զույգը ըստ թեմայի և ստուգեք դրանք CI-ում WCAG contrast գրադարանով՝ ձախողելով տեքստի համար 4.5:1-ից և UI մասերի համար 3:1-ից ցածր արժեքները։ Ուղղեք token-ի մակարդակում. մուգ mapping-ում բարձրացրեք --color-text-muted-ի lightness-ը՝ կոմպոնենտները կարկատելու փոխարեն։ Focus-ին տվեք իր token զույգը, օրինակ՝ --focus-ring և --focus-ring-offset, որոնք սահմանված են ըստ թեմայի, և նկարեք երկգույն ring (outline գումարած հակադրվող box-shadow), որպեսզի այն երևա ցանկացած ֆոնի վրա։ Հետո ևս մեկ անգամ ստուգեք Windows-ի forced-colors ռեժիմում, որտեղ custom գույները փոխարինվում են համակարգային գույներով։

Արագ ուղեցույց

Հայտարարել շերտերի հերթականությունը մեկ անգամ
@layer reset, vendor, tokens, base, components, utilities;
Ներմուծել vendor CSS-ը ցածր շերտ
@import url("vendor.css") layer(vendor);
Hover երանգ ցանկացած գույնից
color-mix(in oklch, var(--color-accent), black 15%)
Փոխել գույնի մեկ ալիքը
oklch(from var(--brand) calc(l + 0.2) c h)
Թեմայից կախված արժեք առանց media queries
color-scheme: light dark; color: light-dark(#111, #eee);
Fluid տառաչափ, որը հարգում է zoom-ը
font-size: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
Կոմպոնենտի մակարդակի responsive կանոն
.wrap { container-type: inline-size; } @container (width > 40rem) { ... }
Typed, անիմացվող custom property
@property --x { syntax: "<length>"; inherits: false; initial-value: 0px; }
Արգելել hex գույները (Stylelint)
"color-no-hex": true
Բաց թողնել էկրանից դուրս բաժինների ռենդերը
content-visibility: auto; contain-intrinsic-size: auto 600px;

Իմ իրական նախագծերում

  • Կենդանիների ապրանքների e-commerce պլատֆորմում design system-ի կանոնները գրել եմ նախագծի AI հրահանգներում. միայն design token-ներ, 7 ֆիքսված breakpoint, ոչ մի hex կամ px կոմպոնենտներում, և թեմայի փոփոխություններ, որոնք միայն additive են։
  • Համատեղ սպասարկում եմ ներքին փաթեթների էկոհամակարգ՝ spacing token-ների core հավաքածուով և մոտ 20 կոմպոնենտից բաղկացած admin UI design system-ով, գումարած design inspector, որը դիզայներներին թույլ է տալիս CSS փոփոխականները կենդանի կարգավորել և պահպանել դրանք source-ում։
  • Այդ էկոհամակարգում պլանավորել եմ անցում semantic token-ների՝ հեռացնելու համար յուրաքանչյուր նախագծի override ֆայլերը, որոնցից ամեն մեկը մոտավորապես 383-ից 2,248 տող էր։
  • Թողարկել եմ բոլոր երեք մոտեցումներով. styled-components՝ մի քանի Next.js storefront-ներում, Tailwind՝ SaaS ադմինում, և CSS Modules՝ մեկ token ֆայլով այս կայքում։

Հարցեր և պատասխաններ

Q1Ի՞նչ է design token-ը, և ինչու՞ երկու շերտ։

Design token-ը անվանված դիզայնի որոշում է, օրինակ՝ գույն, spacing-ի քայլ կամ տևողություն, որը պահվում է մեկ անգամ և հղվում ամենուր, սովորաբար որպես CSS custom property։ Երկու շերտը այն ճկուն է պահում։ Primitive token-ները նկարագրում են, թե ինչ կա, օրինակ՝ --blue-600։ Semantic token-ները նկարագրում են նպատակը, օրինակ՝ --color-accent կամ --color-surface, և հղում են primitive-ներին։ Կոմպոնենտներն օգտագործում են միայն semantic token-ներ։ Այդ դեպքում dark mode-ը, rebrand-ը կամ white-label հաճախորդը պարզապես semantic անուններից primitive-ների նոր mapping է, և կոմպոնենտների կոդը չի փոխվում։ Առանց semantic շերտի դուք ստիպված եք փնտրել ամեն տեղ, որտեղ blue-600 է օգտագործվել, և գուշակել, թե որոնք էին իրականում «accent»։

Q2CSS Modules, styled-components, թե՞ Tailwind նոր Next.js App Router նախագծի համար։

Server components-first հավելվածի համար կընտրեի CSS Modules կամ Tailwind։ Երկուսն էլ build-ի ժամանակ կոմպիլացվում են ստատիկ CSS-ի, runtime JavaScript չեն ավելացնում և աշխատում են server կոմպոնենտներում։ CSS Modules-ը հարմար է այն թիմերին, որոնք սիրում են իսկական CSS գրել token-ներով և մաքուր markup են ուզում. Tailwind-ը հարմար է այն թիմերին, որոնք արագությունն են գնահատում և գոհ են utility class-ներից։ styled-components-ը հենվում է React context-ի և runtime-ում ոճերի ներարկման վրա, ուստի App Router-ում ստիպում է օգտագործել client կոմպոնենտներ և SSR registry, իսկ նախագիծը 2025-ից maintenance mode-ում է։ Կպահեի այն գոյություն ունեցող կոդի բազաներում, որտեղ այն աշխատում է, բայց նորը դրանով չէի սկսի։

Q3Ե՞րբ կօգտագործեիք container query-ներ media query-ների փոխարեն։

Media query-ները viewport-ի մասին են, container query-ները՝ կոմպոնենտի սեփական տարածքի։ Media query-ներ օգտագործում եմ էջի մակարդակի layout-ի համար, օրինակ՝ նավիգացիան փոխելու կամ grid-ի սյունակների քանակը փոխելու համար։ Container query-ներ օգտագործում եմ վերաօգտագործվող կոմպոնենտների համար, որոնք հայտնվում են տարբեր կոնտեքստներում, օրինակ՝ ապրանքի card, որը մի էջում նեղ sidebar-ում է, մյուսում՝ լայն grid-ում։ Wrapper-ի վրա container-type: inline-size-ով և @container կանոնով card-ը հարմարվում է իր իրական լայնությանը։ Սա վերացնում է բագերի մի ամբողջ դաս, երբ կոմպոնենտը մեծ էկրանին ճիշտ է երևում, բայց նեղ սյունակի մեջ սեղմված է։

Q4Ինչպե՞ս եք իրականացնում dark mode-ը՝ առանց սխալ թեմայի «բռնկման»։

Նախ՝ յուրաքանչյուր կոմպոնենտ ստիպեք կարդալ semantic գույների token-ներ, որպեսզի թեման լինի պարզապես արժեքների այլ հավաքածու։ Երկրորդ՝ թեման որոշեք մինչև առաջին paint-ը։ Եթե օգտատերը պահպանված նախընտրություն ունի, կարդացեք այն սերվերում cookie-ից և html էլեմենտի վրա render արեք data-theme, կամ head-ում գործարկեք փոքրիկ inline script, որը այն սահմանում է localStorage-ից։ Եթե նախընտրություն չկա, հետևեք prefers-color-scheme-ին color-scheme-ի և light-dark()-ի միջոցով։ Խուսափեք թեման React effect-ում hydration-ից հետո փոխելուց, որովհետև այդ պահին էջն արդեն paint է արվել default թեմայով, և օգտատերը «բռնկում» է տեսնում։

Q5Ինչպե՞ս եք design system-ը համահունչ պահում ամբողջ թիմում։

Գործիքներով և փոքր API մակերեսով, ոչ թե հիշեցումներով։ Token-ներն ապրում են մեկ ֆայլում, և Stylelint կանոնը արգելում է «հում» գույներն ու պիքսելային արժեքները այլ տեղերում։ Կոմպոնենտները տրամադրում են variant-ների և size-երի փակ հավաքածու՝ կամայական style props-ի փոխարեն։ Ընդհանուր կոմպոնենտները փաստաթղթավորված են կենդանի օրինակներով և ունեն version, այնպես որ օգտագործողները թարմացնում են գիտակցաբար։ Code review-ն ստուգում է նոր միանգամյա արժեքները, և կանոնները գրում եմ նախագծի contribution guide-ում կամ AI հրահանգներում, որպեսզի assistant-ներն էլ հետևեն դրանց։ Երբ ինչ-որ մեկին պետք է մի բան, որը համակարգը չի առաջարկում, այն ճիշտ ձևով ավելացնում ենք համակարգում, այլ ոչ թե լոկալ patch անում։

Q6Ինչպե՞ս եք design system-ը դարձնում accessible։

Accessibility-ն սկսվում է token-ներից. տեքստի և ֆոնի յուրաքանչյուր զույգ պետք է համապատասխանի WCAG կոնտրաստին՝ 4.5:1 սովորական տեքստի համար և 3:1 մեծ տեքստի ու UI մասերի համար, ամեն թեմայում։ Focus ոճերը նույնպես token են և երբեք չեն հեռացվում, միայն վերաոճավորվում են :focus-visible-ով։ Ինտերակտիվ կոմպոնենտներն օգտագործում են իրական էլեմենտներ, օրինակ՝ button՝ գործողությունների համար և a՝ նավիգացիայի համար, իսկ բարդ widget-ները կառուցված են headless primitive-ների վրա, որոնք արդեն մշակում են ստեղնաշարի աջակցությունը, focus trapping-ը և ARIA-ն։ Չափերը հարգում են օգտատիրոջ տառաչափի կարգավորումները rem-ի շնորհիվ, target-ները առնվազն 24 x 24 CSS պիքսել են, իսկ motion token-ները հարգում են prefers-reduced-motion-ը։

Սենիոր մակարդակի հարցեր

S1Ինչպե՞ս կտարածեիք token-ի breaking փոփոխությունը տասնյակ սպառող հավելվածների վրա։

Token package-ին վերաբերվում եմ որպես հանրային API-ի։ Նախ՝ երբեք տեղում չեմ վերանվանում. ավելացնում եմ նոր semantic token-ը, հինը պահում եմ որպես դրան ուղղված alias և նշում եմ deprecated գեներացված TypeScript տիպերում և lint կանոնում, որը զգուշացնում է օգտագործման դեպքում։ Դա թողարկվում է որպես minor տարբերակ, ուստի հավելվածները թարմացվում են առանց կոտրվելու։ Երկրորդ՝ տրամադրում եմ codemod, հաճախ պարզ PostCSS կամ find-and-replace սկրիպտ, որը վերաշարադրում է հին անունները։ Երրորդ՝ հետևում եմ ընդունմանը. CI job-ը, որը յուրաքանչյուր repository-ում փնտրում է deprecated անունները, տալիս է burn-down ցուցակ։ Միայն երբ օգտագործումը հասնում է զրոյի կամ հայտարարված ամսաթվից հետո եմ հեռացնում alias-ը major տարբերակում։ Յուրաքանչյուր հավելվածի visual regression թեստերը բռնում են այն դեպքերը, երբ alias-ն իրականում համարժեք չէր։ Փոխզիջումը ավելի դանդաղ տարածումն է՝ անակնկալ կոտրվածքների բացակայության դիմաց։

S2Build-time թե՞ runtime theming բազմաբրենդ արտադրանքի համար։

CSS custom properties-ով runtime theming-ը իմ default-ն է. մեկ CSS bundle, բրենդը կամ թեման data attribute է, որը վերակապում է semantic token-ները, և փոխարկումն արժե մեկ style recalculation։ Այն հարմար է օգտատիրոջ կողմից միացվող dark mode-ի և տվյալների բազայից բեռնվող tenant branding-ի համար՝ առանց յուրաքանչյուր tenant-ի համար rebuild-ի։ Build-time theming-ը գեներացնում է stylesheet յուրաքանչյուր բրենդի համար. ամեն բրենդ մի փոքր ավելի քիչ CSS է ուղարկում, և դուք ստանում եք compile-time ստուգումներ, բայց յուրաքանչյուր նոր բրենդ պահանջում է build և deploy, իսկ փոխարկումը նշանակում է վերաբեռնում։ Շատ թիմեր համատեղում են դրանք. palette-ները գեներացվում են build-ի ժամանակ token-ների աղբյուրից, իսկ semantic mapping-ը կիրառվում է runtime-ում։ Այն, ինչից խուսափում եմ, React-ում JavaScript theme provider-ով theming-ն է, որովհետև այն ստիպում է client component-ներ և ամեն թեմայի փոփոխության ժամանակ վերառենդերում է ծառը, ինչը վնասում է և՛ server rendering-ին, և՛ ինտերակցիայի արտադրողականությանը։

S3Ինչպե՞ս են Tailwind-ը և token համակարգը համատեղվում։

Դրանք լրացնում են իրար. token-ները որոշում են, թե ինչ արժեքներ կան, իսկ Tailwind-ը դրանք կիրառելու հարմար միջոց է։ Tailwind v4-ը կոնֆիգուրացվում է CSS-ում. դուք token-ները հայտարարում եք @theme բլոկում, և այն գեներացնում է utility-ներ, ինչպիսիք են bg-accent-ը կամ p-4-ը, ինչպես նաև յուրաքանչյուր արժեք բացահայտում է որպես CSS փոփոխական, որը սովորական CSS-ը կարող է կարդալ։ Ես semantic token-ները քարտեզագրում եմ @theme-ի մեջ և զրոյացնում default palette-ը --color-*: initial-ով, որպեսզի ոչ ոք չկարողանա ձեռք մեկնել կամայական blue-500-ին։ Կամայական արժեքները, ինչպես p-[13px]-ը, թույլատրված են, բայց նշվում են review-ում։ Բարդ կոմպոնենտները դեռ կարող են օգտագործել CSS Modules, որոնք կարդում են նույն փոփոխականները։ Utility-ների իրական ռիսկը շեղումն է՝ միանգամյա արժեքների և երկար, անընթեռնելի class ցուցակների պատճառով. լուծումը ընդհանուր կոմպոնենտային primitive-ների փոքր հավաքածուն և lint կանոններն են, ոչ թե Tailwind-ից հրաժարվելը։

Բառարան

Cascade layer
Ոճերի անվանված խումբ (@layer)։ Ավելի ուշ շերտերը հաղթում են ավելի վաղերին՝ դեռ մինչև specificity-ի համեմատությունը։
Specificity
Բրաուզերի գնահատականն այն մասին, թե որքան ճշգրիտ է selector-ը. այն լուծում է կոնֆլիկտները նույն շերտի և origin-ի ներսում։
OKLCH
Գունային տարածք՝ ընկալվող lightness-ով, chroma-ով և hue-ով, որտեղ հավասար քայլերն աչքին հավասարապես տարբեր են թվում։
color-mix()
CSS ֆունկցիա, որը խառնում է երկու գույն ընտրված գունային տարածքում, օրինակ՝ hover երանգներ ստանալու համար։
Relative colour syntax
Նոր գույնի կառուցում գոյություն ունեցողից՝ փոխելով ընտրված ալիքները. oklch(from var(--x) l c h)։
DTCG format
W3C community group-ի JSON ստանդարտը design token-ների համար, որը կարդում են դիզայն և build գործիքները։
Style Dictionary
Build գործիք, որը token-ների մեկ աղբյուրը վերածում է CSS, JavaScript, iOS և Android արդյունքների։
@property
Գրանցում է custom property՝ տիպով, սկզբնական արժեքով և ժառանգմամբ, որպեսզի այն կարողանա անիմացվել և վալիդացվել։
Style recalculation
Բրաուզերի քայլը, որը հաշվում է յուրաքանչյուր էլեմենտի վերջնական ոճերը. մեծ անվավերացումները այն դանդաղեցնում են։
@scope
CSS at-rule, որը սահմանափակում է ոճերը ենթածառով, ըստ ցանկության՝ ներքևի սահմանով, որտեղ դրանք կանգ են առնում։

Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։

Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։