Motiva
Мои коллекции
← каталог

Design Tokens

Дизайн-система каталога: шесть ручек — тон, насыщенность и светлота акцента, скругление, плотность и масштаб текста, — из которых считается всё остальное. Крутится вживую, готовый CSS копируется одной кнопкой.

cssclickbasic~2 КБreduced-motion ✓
🎛 Настройки — крути и копируй

Установка

npx shadcn@latest add https://motiva.pages.dev/r/design-tokens.json
Что нужно в проекте до установки
  1. components.json в корне — с алиасами в форме"components": "@/components". Нет файла — CLI не поймёт, куда класть.
  2. Алиас @ объявлен дважды: "paths": {"@/*": ["./src/*"]} в корневом tsconfig.json (в Vite он часто содержит только references — тогда CLI создаёт папку@) и resolve.alias в конфиге сборщика.
  3. Токены подключены один раз на приложение:import "@/styles/motiva-tokens.css" — они приезжают вместе с записью. Без них секция потеряет цвета, ритм и тему.
  4. Своя типографика не должна спорить: если у вас на обёрткеtext-align: center, он протечёт внутрь блоков.
  5. Проект без TypeScript? Всё равно нужен минимальныйtsconfig.json с pathsи "tsx": true в components.json: CLI грузит tsconfig безусловно, а с "tsx": false идёт за jsconfig.json и падает так же. Приехавший рядом.tsx можно не трогать — вам нужен.vanilla.js.

Экспорт

↓ Скачать .zip

Код

/**
 * 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