CSS և design system-ներ
Design token-ներ, սանդղակներ, թեմաներ և ձեր stack-ին համապատասխան ոճավորման գործիքը։
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-ները նույնպես նույն ֆայլում են։
: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-ը, այլապես օգտատերերը կտեսնեն սխալ թեմայի «բռնկում»։
: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-ը անակնկալ չլինի այն օգտագործող տասը հավելվածների համար։
// 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-ների կարիքը։
.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-ի։
Լավագույն պրակտիկաներ
- Token-ների երկու շերտ. primitive-ներ՝ պալիտրայի համար, semantic token-ներ՝ կոմպոնենտների համար։
- Կոմպոնենտներն օգտագործում են միայն semantic token-ներ, երբեք «հում» արժեքներ։
- Պահեք breakpoint-ների մեկ ցուցակ և spacing-ի մեկ սանդղակ մեկ ֆայլում։
- Նախընտրեք container query-ներ այն կոմպոնենտների համար, որոնք հայտնվում են տարբեր layout-ներում։
- Բարդ widget-ները կառուցեք accessible headless primitive-ների վրա։
- Token-ների փոփոխությունները դարձրեք additive և version տվեք ընդհանուր փաթեթներին։
- Ստուգեք գույների կոնտրաստը տեքստի և ֆոնի յուրաքանչյուր զույգի համար ամեն թեմայում։
Տարածված սխալներ
- 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-ից առաջ։
@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-ով։
: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 ծառայությամբ), որովհետև մեկ արժեքը կարող է միանգամից տեղաշարժել հարյուրավոր կոմպոնենտներ։
{
"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-ով։
@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;
}
Իրական իրավիճակներ
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-ը։ Կոմպոնենտների տրամաբանությունը չի փոխվում, փոխվում են միայն արժեքները։
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-ում, որպեսզի նոր կոդը հայտնվի ճիշտ շերտում։
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, որը սահմանափակում է ոճերը ենթածառով, ըստ ցանկության՝ ներքևի սահմանով, որտեղ դրանք կանգ են առնում։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։