← каталог
Design Tokens
Дизайн-система каталога: шесть ручек — тон, насыщенность и светлота акцента, скругление, плотность и масштаб текста, — из которых считается всё остальное. Крутится вживую, готовый CSS копируется одной кнопкой.
cssclickbasic~2 КБreduced-motion ✓
🎛 Настройки — крути и копируй
Установка
npx shadcn@latest add https://motiva.pages.dev/r/design-tokens.jsonЧто нужно в проекте до установки
components.jsonв корне — с алиасами в форме"components": "@/components". Нет файла — CLI не поймёт, куда класть.- Алиас
@объявлен дважды:"paths": {"@/*": ["./src/*"]}в корневомtsconfig.json(в Vite он часто содержит толькоreferences— тогда CLI создаёт папку@) иresolve.aliasв конфиге сборщика. - Токены подключены один раз на приложение:
import "@/styles/motiva-tokens.css"— они приезжают вместе с записью. Без них секция потеряет цвета, ритм и тему. - Своя типографика не должна спорить: если у вас на обёртке
text-align: center, он протечёт внутрь блоков. - Проект без TypeScript? Всё равно нужен минимальный
tsconfig.jsonсpathsи"tsx": trueвcomponents.json: CLI грузит tsconfig безусловно, а с"tsx": falseидёт заjsconfig.jsonи падает так же. Приехавший рядом.tsxможно не трогать — вам нужен.vanilla.js.
Экспорт
Код
/**
* Motiva Design Tokens — общий язык всех секций и блоков.
*
* Зачем: страницу нельзя собрать из чужих друг другу кусков. Секция из одной записи и
* секция из другой встают рядом только если у них общие отступы, типографика, радиусы,
* цвета и — что важнее всего — общий характер движения.
*
* Как устроено:
* 1. АКЦЕНТ разложен на компоненты (светлота / насыщенность / тон). Меняешь один тон —
* перекрашивается вся система, включая производные состояния. Это и есть «одна ручка».
* 2. СЕМАНТИКА (--ds-bg, --ds-fg, …) переключается темой; всё остальное от темы не зависит.
* 3. Тема: по умолчанию системная, класс .dark / .light на <html> имеет приоритет.
* 4. Блоки ссылаются на токены С ФОЛБЭКОМ — var(--ds-accent, oklch(0.72 0.16 300)) —
* поэтому скопированный в чужой проект компонент работает и без этого файла,
* а с ним начинает слушаться темы. Жёсткой зависимости нет by design.
*/
:root {
/* ═══ РУЧКИ ═══ Всё, что настраивается. Ниже — только производные от них.
Меняешь шесть чисел — меняется вся система, включая состояния и шкалы. */
/* Акцент: тон / насыщенность / светлота */
--ds-accent-h: 300; /* 0–360: главное число ре-брендинга */
--ds-accent-c: 0.16;
--ds-accent-l: 0.62;
/* Плотность и кегль: множители ко ВСЕЙ шкале сразу */
--ds-space-scale: 1; /* 0.8 — компактно, 1.2 — просторно */
--ds-text-scale: 1;
/* Скругление: базовое, остальные считаются от него */
--ds-radius: 14px;
/* ── Шрифты ──────────────────────────────────────────────────────────── */
--ds-font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--ds-font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
--ds-tracking-tight: -0.03em;
--ds-tracking-display: -0.045em;
--ds-leading-tight: 0.94;
--ds-leading-body: 1.55;
/* ── Форма ───────────────────────────────────────────────────────────── */
--ds-radius-pill: 999px;
--ds-container: 1120px;
--ds-gutter: clamp(18px, 5vw, 54px);
/* ── Движение: ТРИ кривые на весь продукт ────────────────────────────── */
/* Разнобой easing'ов читается как самоделка, даже когда каждая анимация хороша. */
--ds-ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* появление, въезд */
--ds-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* перемещение из А в Б */
--ds-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* отклик на действие */
--ds-fast: 0.18s;
--ds-base: 0.32s;
--ds-slow: 0.6s;
/* ── Семантика: светлая тема (значение по умолчанию) ─────────────────── */
--ds-bg: oklch(0.99 0.004 265);
--ds-surface: oklch(0.97 0.006 265);
--ds-surface-2: oklch(0.94 0.008 265);
--ds-fg: oklch(0.22 0.02 265);
--ds-muted: oklch(0.52 0.02 265);
--ds-border: oklch(0.9 0.006 265);
--ds-accent-fg: oklch(0.99 0.004 265);
/* статусы: формам нужен цвет ошибки, и он обязан быть системным — иначе каждая форма
заводит свой красный, и рядом они выглядят из разных продуктов */
--ds-danger: oklch(0.55 0.2 25);
--ds-success: oklch(0.58 0.15 150);
--ds-shadow: 0 10px 34px oklch(0.4 0.03 265 / 0.14);
--ds-shadow-lg: 0 26px 60px oklch(0.4 0.03 265 / 0.2);
color-scheme: light;
}
/**
* ═══ ПРОИЗВОДНЫЕ ═══ вынесены в ОТДЕЛЬНОЕ правило, и вот почему это принципиально.
*
* `var()` внутри значения кастомного свойства подставляется там, где это свойство
* ОБЪЯВЛЕНО, а наследуется уже готовый результат. Если бы производные считались только
* на `:root`, то смена ручки на каком-нибудь блоке ниже по дереву не меняла бы ровным
* счётом ничего: потомок унаследовал бы значение, посчитанное со старой ручкой.
* Обещание «одно число перекрашивает всё» работало бы только для страницы целиком.
*
* Поэтому все формулы продублированы для `.ds-scope`: вешаешь класс на любой контейнер,
* задаёшь ему свои ручки — и внутри пересчитывается весь набор. Так две страницы
* собираются из одних секций и различаются парой чисел (см. landing-studio,
* landing-product и landing-device).
*/
:root,
.ds-scope {
/* ── Акцент и его состояния ──────────────────────────────────────────── */
--ds-accent: oklch(var(--ds-accent-l) var(--ds-accent-c) var(--ds-accent-h));
--ds-accent-hover: oklch(calc(var(--ds-accent-l) + 0.06) var(--ds-accent-c) var(--ds-accent-h));
--ds-accent-soft: oklch(var(--ds-accent-l) var(--ds-accent-c) var(--ds-accent-h) / 0.16);
--ds-accent-ring: oklch(var(--ds-accent-l) var(--ds-accent-c) var(--ds-accent-h) / 0.35);
/* второй тон — для градиентов; сдвиг по кругу, а не отдельный хардкод */
--ds-accent-2: oklch(var(--ds-accent-l) var(--ds-accent-c) calc(var(--ds-accent-h) - 80));
/* ── Отступы: модульная шкала × общий множитель плотности ────────────── */
--ds-1: calc(4px * var(--ds-space-scale));
--ds-2: calc(8px * var(--ds-space-scale));
--ds-3: calc(12px * var(--ds-space-scale));
--ds-4: calc(16px * var(--ds-space-scale));
--ds-5: calc(24px * var(--ds-space-scale));
--ds-6: calc(36px * var(--ds-space-scale));
--ds-7: calc(54px * var(--ds-space-scale));
--ds-8: calc(80px * var(--ds-space-scale));
--ds-9: calc(120px * var(--ds-space-scale));
/* ── Типографика: везде clamp, ни одного брейкпоинта, × общий множитель ─ */
--ds-text-xs: calc(clamp(0.72rem, 0.7rem + 0.1vw, 0.78rem) * var(--ds-text-scale));
--ds-text-sm: calc(clamp(0.82rem, 0.8rem + 0.15vw, 0.9rem) * var(--ds-text-scale));
--ds-text-md: calc(clamp(0.95rem, 0.9rem + 0.2vw, 1.05rem) * var(--ds-text-scale));
--ds-text-lg: calc(clamp(1.15rem, 1rem + 0.6vw, 1.4rem) * var(--ds-text-scale));
--ds-text-xl: calc(clamp(1.5rem, 1.2rem + 1.4vw, 2.2rem) * var(--ds-text-scale));
--ds-display: calc(clamp(2.2rem, 1rem + 7vw, 5.4rem) * var(--ds-text-scale));
/* ── Скругления: соотношения к базовому, а не три независимых числа ──── */
--ds-radius-sm: calc(var(--ds-radius) * 0.57);
--ds-radius-lg: calc(var(--ds-radius) * 1.43);
}
/* Тёмная тема: системная по умолчанию… */
@media (prefers-color-scheme: dark) {
:root:not(.light) {
--ds-accent-l: 0.72;
--ds-bg: oklch(0.14 0.02 275);
--ds-surface: oklch(0.18 0.02 275);
--ds-surface-2: oklch(0.22 0.025 275);
--ds-fg: oklch(0.95 0.01 265);
--ds-muted: oklch(0.72 0.02 265);
--ds-border: oklch(0.3 0.02 275);
--ds-accent-fg: oklch(0.14 0.02 275);
--ds-danger: oklch(0.68 0.19 25);
--ds-success: oklch(0.72 0.15 150);
--ds-shadow: 0 10px 34px oklch(0 0 0 / 0.45);
--ds-shadow-lg: 0 26px 60px oklch(0 0 0 / 0.6);
color-scheme: dark;
}
}
/* …и явный класс, который системную настройку перебивает (тумблер темы). */
:root.dark {
--ds-accent-l: 0.72;
--ds-bg: oklch(0.14 0.02 275);
--ds-surface: oklch(0.18 0.02 275);
--ds-surface-2: oklch(0.22 0.025 275);
--ds-fg: oklch(0.95 0.01 265);
--ds-muted: oklch(0.72 0.02 265);
--ds-border: oklch(0.3 0.02 275);
--ds-accent-fg: oklch(0.14 0.02 275);
--ds-danger: oklch(0.68 0.19 25);
--ds-success: oklch(0.72 0.15 150);
--ds-shadow: 0 10px 34px oklch(0 0 0 / 0.45);
--ds-shadow-lg: 0 26px 60px oklch(0 0 0 / 0.6);
color-scheme: dark;
}
/* Уважение к системной настройке движения — один раз на всю систему,
чтобы каждая секция не переписывала это у себя. */
@media (prefers-reduced-motion: reduce) {
:root {
--ds-fast: 0.01ms;
--ds-base: 0.01ms;
--ds-slow: 0.01ms;
}
}
лицензия: MITавтор: Motivaисточник: оригинал (Motiva)v1.0.0