Mamkin UI — Design System

Роль: Product Designer Период: 2025 — настоящее время Продукт: Дизайн-система mamkin-mini-app (Mom’s Helper)

Единая страница дизайн-системы mamkin-mini-app: цвета, типографика, foundations, layout, компоненты, иконки — из реального кода src/. Контекст и метрики продукта — в кейсе Mom’s Helper.

Цветовые токены проекта

Компонент не знает своего цвета — он знает роль («фон карточки», «вторичный текст»), а значение подставляет тема. Поэтому имя одно, а значений два: светлое и тёмное. У акцента ребёнка — семь.

Образец — реальный цвет из src/index.css, на диагональной подложке: иначе полупрозрачные *-bg не видно ни в одной теме. Примитив и HEX — в разделе Figma ↔ Код.

Semantic tokens — Light mode

Двенадцать переменных задают всю светлую тему — фоны, текст, границы.

ОбразецCSS-переменнаяSemanticГде используется
--bg-pagebg/pageФон страницы
--bg-cardbg/cardФон карточки/шита
--bg-card2bg/card2Фон вложенной группы (settings)
--bg-inputbg/inputФон полей ввода, неактивные кнопки
--border-cardborder/cardОбводка карточек/полей
--text-primarytext/primaryОсновной текст
--text-secondarytext/secondaryВторичный текст
--text-placeholdertext/placeholderПлейсхолдеры
--row-dividerrow/dividerРазделители строк списков
--nav-bgnav/bgФон нижней навигации
--nav-bordernav/borderОбводка нижней навигации
--icon-btn-bgicon-btn/bgФон Button variant=subtle

Semantic tokens — Dark mode

Те же двенадцать имён, другие значения. В Figma это Modes одной переменной, не вторая переменная.

ОбразецCSS-переменнаяSemanticГде используется
--bg-pagebg/pageФон страницы
--bg-cardbg/cardФон карточки/шита
--bg-card2bg/card2Фон вложенной группы (settings)
--bg-inputbg/inputФон полей ввода, неактивные кнопки
--border-cardborder/cardОбводка карточек/полей
--text-primarytext/primaryОсновной текст
--text-secondarytext/secondaryВторичный текст
--text-placeholdertext/placeholderПлейсхолдеры
--row-dividerrow/dividerРазделители строк списков
--nav-bgnav/bgФон нижней навигации
--nav-bordernav/borderОбводка нижней навигации
--icon-btn-bgicon-btn/bgФон Button variant=subtle

Semantic tokens — Status & CTA

Четыре статуса, у каждого пара: насыщенный цвет для текста и иконки плюс фон плашки — тот же цвет, 15% в тёмной теме и 12% в светлой.

Dark

ОбразецCSS-переменнаяSemanticГде используется
--successsuccessПодтверждение, идёт/активно
--success-bgsuccess-bgПодтверждение, идёт/активно — фон плашки
--ds-warningwarningПредупреждение, error-баннер Dashboard
--ds-warning-bgwarning-bgПредупреждение, error-баннер Dashboard — фон плашки
--ds-dangerdangerУдаление, ошибки ввода
--ds-danger-bgdanger-bgУдаление, ошибки ввода — фон плашки
--ds-infoinfoКормление в EventRow/History
--ds-info-bginfo-bgКормление в EventRow/History — фон плашки

Light

ОбразецCSS-переменнаяSemanticГде используется
--successsuccessПодтверждение, идёт/активно
--success-bgsuccess-bgПодтверждение, идёт/активно — фон плашки
--ds-warningwarningПредупреждение, error-баннер Dashboard
--ds-warning-bgwarning-bgПредупреждение, error-баннер Dashboard — фон плашки
--ds-dangerdangerУдаление, ошибки ввода
--ds-danger-bgdanger-bgУдаление, ошибки ввода — фон плашки
--ds-infoinfoКормление в EventRow/History
--ds-info-bginfo-bgКормление в EventRow/History — фон плашки

Semantic tokens — Accent ребёнка

Не зависит от темы — выбирается в настройках ребёнка. Строка — вариант, колонка — роль: --ds-accent красит CTA и аватар, --ds-accent-light — подложку, --accent-text — текст поверх неё (светлее: основной на подложке не проходит по контрасту), --ds-accent-border — обводку и кольцо фокуса. В коде это четыре переменные на семь вариантов, значение переключает [data-accent]; в Figma придётся завести все 28, и нейтральный вариант там зовётся …/default, а не gray.

Вариант--ds-accent--ds-accent-light--accent-text--ds-accent-border
gray
Без цвета — имя в Figma …/default
accent/defaultaccent-light/defaultaccent-text/defaultaccent-border/default
purple
Фиолетовый
accent/purpleaccent-light/purpleaccent-text/purpleaccent-border/purple
blue
Синий
accent/blueaccent-light/blueaccent-text/blueaccent-border/blue
green
Зелёный
accent/greenaccent-light/greenaccent-text/greenaccent-border/green
pink
Розовый
accent/pinkaccent-light/pinkaccent-text/pinkaccent-border/pink
orange
Оранжевый
accent/orangeaccent-light/orangeaccent-text/orangeaccent-border/orange
teal
Бирюзовый
accent/tealaccent-light/tealaccent-text/tealaccent-border/teal

Типографика

Четырнадцать текстовых стилей на всё приложение. Стиль — это не «размер шрифта», а связка размер + вес + интерлиньяж + трекинг: применяется одним кликом и одинаково выглядит везде.

Шрифт

Аа Бб Вв Гг Дд · Aa Bb Cc · 0123456789

Своего веб-шрифта нет — стек -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif. Интерфейс берёт шрифт платформы: на iOS это SF Pro, на Android — Roboto. Плюс — ноль килобайт загрузки и родное для системы начертание; минус — метрики на разных платформах не совпадают, поэтому нигде нет вёрстки, которая держится на точной ширине строки.

Насыщенность — четыре веса

400 · RegularСон 3 ч 40 минfont-normal · 6 мест, только там, где нужен именно невыделенный текст
500 · MediumСон 3 ч 40 минfont-medium · 67 мест, рабочий вес интерфейса
600 · SemiboldСон 3 ч 40 минfont-semibold · 49 мест, заголовки строк и секций
700 · BoldСон 3 ч 40 минfont-bold · 29 мест, цифры и заголовки экранов

Иерархию держит вес, а не размер: на 14px три стиля и на 12px ещё три — внутри пары размеров они различаются только весом. Поэтому шкала размеров такая короткая.

Интерлиньяж — часть стиля, а не следствие размера

100% — leading-none
254
мл
143% — text-sm
Пока нет записей
за сегодня

У крупных цифр интерлиньяж сжат до 100%, иначе число «висит» в пустоте и ломает высоту карточки. У текста — 143%, иначе абзац не читается. Одно и то же значение обоим не подходит, поэтому оно и вшито в стиль.

Text Styles 14 стилей

«Образец» — как стиль выглядит на самом деле, «Semantic» — имя Text Style для Figma, «Реализация» — класс, которым он набран в коде. Переменные, из которых стиль собран внутри Figma (font-size/*, weight/*), — в разделе Figma ↔ Код.

ОбразецSemanticРеализацияГде используется
254 млNumber
24 / 700 · 100%
text-2xl font-bold leading-noneКрупные цифры статистики (StatCard)
СтатистикаPageTitle
20 / 700 · 140%
text-xl font-boldЗаголовки экранов и разделов Главной
Добавить ребёнкаModalTitle
18 / 700 · 156%
text-lg font-boldЗаголовки модалок и разделов (AddChildModal, Settings, Premium)
КормлениеSheetHeader
16 / 600 · 150%
text-base font-semiboldЗаголовок шторки и карточки ребёнка
3 ч 40 минTileValue
14 / 600 · 125%
text-sm font-semibold leading-tightStatusTile — есть в коде, в проде не используется
Ночной режимBodyPrimary
14 / 500 · 143%
text-sm font-mediumЗаголовок строки списка (ListRow); в EventRow — semibold
Пока нет записей за сегодняBody
14 / 400 · 143%
text-smЗагрузка, ошибка, пусто, описания форм — самый частый паттерн, около 40 мест
ИзменитьAccentCaption
12 / 600 · 133%
text-xs font-semiboldКнопки статус-бара, бейдж Premium, дельта в Статистике
КормлениеButtonCaption
12 / 500 · 133%
text-xs font-mediumПодпись под иконкой действия (secondary-кнопки)
2 часа назадBodySecondary
12 / 400 · 133%
text-xsВторая строка в списках, подписи без явного веса
УведомленияSectionLabel
11 / 600 · Auto · трекинг 5%
text-[11px] font-semibold + inline letterSpacing:'0.05em'Заголовки секций, UPPERCASE
ГлавнаяTabLabel
11 / 500 · 100%
text-[11px] font-medium leading-noneПодпись таба нижней навигации
PREMIUMSmallBadge
10 / 500 · Auto
text-[10px]Маленький бейдж (Badge size=sm, вес — из базового класса)
скороMicroBadge
8 / 500 · 100%
text-[8px]Бейдж «скоро» (Badge size=xs)

Layout & grid

Одноколоночный мобильный контейнер с вертикальным скроллом, без классической сетки колонок и без брейкпоинтов — это Telegram Mini App с одним форм-фактором, а не адаптивный сайт под разные вьюпорты. Ниже — реальный экран в масштабе 1:1; разметка включается по одному слою, чтобы каждое правило было видно отдельно, а не всё сразу.

safe-area-top0px
16px
20px · mb-5
Быстрая запись
12px · mb-3
10px · mb-2.5
20px · mb-5
Дети
8px · mb-2
16px
Главная
История
Статистика
Настройки
10px, фикс.
env(safe-area-inset-bottom)20px

Мокап в масштабе 1:1: ширина фрейма 375px и все размеры внутри равны фактическим px из кода, без уменьшения. Переключатели сверху включают по одному слою разметки — на реальном экране, а не на абстрактной схеме.

Поля страницы — px-4, 16px с каждой стороны, одинаково на всех вкладках. Классической сетки колонок нет: ширину контента задают поля и потолок контейнера max-w-md (448px), как в Apple HIG — margins плюс readable width, а не N колонок.

Вертикальный ритм собран из пяти чисел шкалы space: 20px между секциями, 12px под заголовком секции, 10px и 8px внутри сеток. Отдельного токена «ритм» нет — каждый интервал берёт своё значение из общей шкалы.

Локальные сетки внутри секций — обычный CSS Grid на 1–4 колонки, с тем же gap, что в item spacing. Четыре колонки — максимум, дальше кнопка перестаёт быть тап-таргетом по ширине.

Верхний отступ берётся из Telegram API и работает только в реальном fullscreen-режиме, вне его всегда 0. Нижний читает нативный env(safe-area-inset-bottom) и от режима Telegram не зависит. Измерено на iPhone 12 mini в Telegram: 20px, то есть пилюля навигации отстоит от края экрана на 10px (фикс.) + 20px = 30px, а не на фиксированные 10px. Инсет самой пилюли — 20px по бокам, шире полей контента (16px): так она не сливается с интерфейсом.

Всё, по чему надо попасть пальцем, — не ниже 44px, минимума Apple HIG: строка списка 62px, кнопка быстрой записи 96px, таб навигации 57px. Жёлтым — то, что ниже 44px осознанно: иконка-действие в шапке секции (32px) и бейдж в строке (32px) сидят внутри уже крупной цели и не являются основным действием экрана.

Проверка: паддинг + line-height = высота

Заявленная высота Button — это не «на глаз», а сумма реального вертикального паддинга и реального line-height текста внутри (line-height уже посчитан на Typography). Проверка по всем трём размерам:

РазмерПаддинг (2×)МежстрочныйСуммаЗаявлено (min-h)Сходится?
smpy-1.5 = 12pxButtonCaption = 16px28px32px+4px запас
mdpy-3 = 24pxBodyPrimary = 20px44px44pxточно совпадает
lgpy-3.5 = 28pxBodyPrimary = 20px48px52px+4px запас

У md паддинг подобран так, что паддинг и строка текста сходятся ровно в 44px: минимум тап-таргета Apple HIG получается не отдельным решением, а следствием реальной типографики. У sm и lg — по +4px запаса. То же правило держит и остальные высоты: у всего, по чему надо попасть пальцем, ⩾44px — поэтому h-11/min-h-11 самое частое высотное значение в коде (строка настроек, поле ввода, Button md). Осознанные исключения — Button size=sm (32px) и Switch (24px): оба сидят внутри уже крупной цели, а не работают основным действием экрана. Единой формулы «высота такая-то» нет — каждый компонент берёт паддинг из шкалы space сам, разбор по компонентам — в их карточках в разделе Компоненты.

Компоненты

Компоненты приложения по одному — реальные варианты, размеры и состояния из кода, не выдуманные заново. Разделены на примитивы (неделимый контрол, одна роль) и составные (несколько слотов, повторяющиеся элементы или прямой вызов другого компонента внутри).

Все компоненты разом 22 компонента

Чем компоненты отличаются друг от друга и какие вообще ни на что не реагируют. Статичный — состояний нет вовсе: компонент только рисует, нажатие и фокус живут на элементе вокруг. Состояния названы здесь по смыслу и одинаково, чтобы строки сравнивались; в самих блоках имена повторяют код — checked у Switch, selected у ChipSelect, active у Button и BottomNav.

КомпонентТипСоставСостояния
ButtonпримитивИконка и подпись; 5 вариантов заливки × 3 размеранажат · фокус · выключен · загрузка · выбран
SwitchпримитивТрек и бегунок, без слотоввыбран · нажат · фокус · выключен
BadgeпримитивТолько текст; 6 тонов × 3 размерастатичный
AvatarпримитивЭмодзи, фото или инициал; 3 размерастатичный
FieldпримитивПодпись и поверхность — обёртка для Input и SelectFieldстатичный
InputпримитивПодпись и поле вводафокус · выключен
SliderпримитивДорожка и бегунокфокус · выключен
ErrorNoteпримитивИконка и текст ошибкистатичный
SkeletonпримитивЗаглушка загрузки; 3 варианта формыстатичный
AccentPlateпримитивАкцентный фон и слот под содержимоестатичный
PopoverPanelпримитивТолько поверхность попапа, содержимое передаётся снаружистатичный
ChipSelectсоставнойРяд пилюль: иконка и подпись; обычный и compactвыбран · нажат · фокус · выключен
QuickActionButtonсоставнойЭмодзи, подпись, бейдж; primary и secondaryвыбран · нажат · фокус · выключен
ListRowсоставнойИконка или аватар, заголовок, подпись, заметка, хвостнажат · фокус · выключен · приглушён
PageHeaderсоставнойAccentPlate, переключатель ребёнка, действия, слот под контентзагрузка
BottomNavсоставнойЧетыре таба: иконка и подписьвыбран · нажат
ReorderableRowсоставнойListRow плюс ручка перетаскиванияперетаскивание · фокус · приглушён
StatCardсоставнойЗаголовок, значение с единицей, вторая строка, бейджпусто · приглушён
SelectFieldсоставнойField плюс список опцийраскрыт · фокус · выключен
TimeSelectсоставнойField плюс колёса часов и минутраскрыт · фокус · выключен
SliderFieldсоставнойПодпись, Slider, текстовая подсказкастатичный
SheetсоставнойШторка снизу: ручка, шапка, тело, секции, подвалраскрыт · свайп

Примитивы11 компонентов

Неделимые элементы: один контрол — одна роль. Внутри нет других компонентов, дальше не раскладываются. Всё, что ниже в разделе «Составные», собрано из них и из этих же токенов.

Button ui-kit/Button.tsx

Кнопка действия — текст, иконка или и то и другое. Без children рендерится как квадратная icon-only кнопка.

Состояния вживую

← или зажмите любую выше ← или Tab со страницы

Настоящие CSS-псевдоклассы, не имитация стилями.

5 вариантов × md

active:opacity-70 — общий для всех вариантов.

3 размера — текст и icon-only (одна и та же высота)

sm / 32px
md / 44px
lg / 52px

Кнопка — Button (3 размера)

12px
12px
6px
6px
sm
20px
20px
12px
12px
md
24px
24px
14px
14px
lg
ВариантВертикальныйГоризонтальныйЗазорВысотаТекст
smpy-1.5 · 6pxpx-3 · 12pxgap-1.5 · 6pxmin-h-8 · 32pxfont-size/12 · weight/medium
mdpy-3 · 12pxpx-5 · 20pxgap-1.5 · 6pxmin-h-11 · 44pxfont-size/14 · weight/medium
lgpy-3.5 · 14pxpx-6 · 24pxgap-1.5 · 6pxmin-h-13 · 52pxfont-size/14 · weight/medium

Радиус rounded-control и вес weight/medium общие у всех трёх. Зазор появляется только с иконкой.

Свойства

ЭлементЗначениеТокен
Подпись14/500 — md и lg; 12/500 — smBodyPrimary · ButtonCaption
Скругление10pxrounded-control
Обводка1px у secondary и subtle; прозрачная (не отсутствует — иначе active сдвигал бы кнопку на 1px) у primary и danger; нет у ghoststroke/thin · border/card
Высота — минимум, не фикс32 / 44 / 52pxmin-h-8 · min-h-11 · min-h-13
Отступы по бокам12 / 20 / 24pxspace/12 · space/20 · space/24
Зазор иконка ↔ подпись6pxgap-1-5
Заливкаакцент / карточка / danger / прозрачная / icon-btn--ds-accent · --bg-card · --ds-danger · --icon-btn-bg
Цвет подписибелый на заливке, иначе основной или вторичный--text-primary · --text-secondary

Варианты

ВариантЗаливкаОбводкаВыключено
primaryvar(--ds-accent), заливкаtransparent (плейсхолдер)полная смена палитры → нейтральный серый, не притушенный акцент
secondaryvar(--bg-card), заливкаvar(--border-card), 1pxпросто opacity-50 — и так нейтральный
dangervar(--ds-danger), заливкаtransparent (плейсхолдер)та же смена палитры, что у primary
ghosttransparentнетopacity-50
subtlevar(--icon-btn-bg), полупрозрачнаяvar(--border-card), 1pxopacity-50 · для кнопок на цветном/акцентном фоне

Состояния

СостояниеЧто меняетсяТокен
defaultБазовый вид
pressedКнопка гаснетopacity/70
activeПроп подменяет вариант на primary — «выбрано» у переключателей вида графика. Тот же проп и тот же смысл, что у QuickActionButton и BottomNav--ds-accent
focus-visibleКольцо 2px с зазором 2px, бледный акцентfocus-ring · --ds-accent-border
disabledprimary и danger получают нейтральную заливку вместо акцента; остальные просто гаснут--bg-input · opacity/50
loadingСпиннер вместо содержимого, кнопка не нажимается

Пропы

ПропНазначение
variantprimary · secondary · danger · ghost · subtle
sizesm · md (по умолчанию) · lg
iconИконка перед подписью; без подписи — квадратная icon-only кнопка
activeПодменяет вариант на primary — «выбрано» у переключателей вида графика
loadingСпиннер вместо содержимого, кнопка не нажимается
fullWidthРастянуть на всю ширину контейнера

Switch ui/switch.tsx

Бинарный переключатель вкл/выкл — обёртка над Radix Switch.

ui/switch.tsx

Трек 44×24px, бегунок 20×20px.

Switch — переключатель

ЭлементЗначениеТокен
Трек44 × 24pxw-11 · h-6
Бегунок20 × 20pxw-5 · h-5
Сдвиг включённого20pxtranslate-x-5

Отступов нет: инсет под бегунок создаёт прозрачная рамка border-2, отсюда 24 − 2×2 = 20px. Библиотечный shadcn поверх Radix.

Свойства

ЭлементЗначениеТокен
Трек44 × 24pxw-11 · h-6
Бегунок20 × 20px, белый, сдвиг 20pxh-5 · space/20
Скруглениеполное — и трек, и бегунокrounded-full
Заливка вкл / выклакцент / фон поля--ds-accent · --bg-input
Тень бегункамягкая, 25% чёрногоshadow-lg · opacity/25
focus-visibleкольцо 2px внутрь--ds-accent-border
disabledгаснет целикомopacity/50
pressedнет — его не было ни у одного из двух прежних компонентов, при объединении не добавляли

Состояния

СостояниеЧто меняетсяТокен
checked / uncheckedЗаливка трека и сдвиг бегунка на 20px--ds-accent · --bg-input
focus-visibleКольцо 2px внутрь--ds-accent-border
disabledГаснет целикомopacity/50
pressedНет — не было ни у одного из двух прежних компонентов, при объединении не добавляли

Пропы

ПропНазначение
checkedСостояние переключателя
onCheckedChangeОбработчик переключения
disabledЗаблокирован

Badge ui-kit/Badge.tsx

Бейдж-пилюля со статусом или тегом. Форма всегда rounded-full и не переопределяется по месту: пилюля не нажимается, прямоугольник рядом — нажимается, разная форма это подсказка. Не интерактивен.

6 tone × 3 size

neutral accent success warning danger info
скоро Premium
скоро Premium

Свойства

ЭлементЗначениеТокен
Подпись — md12/500ButtonCaption
Подпись — sm10/500SmallBadge
Подпись — xs8/500MicroBadge
Отступы10 × 4px у md · 8 × 2px у sm · 6 × 2px у xsspace/10 · space/8 · space/6
Скруглениеполное, всегдаrounded-full
Зазор иконка ↔ подпись4pxgap-1
Тона (6)полупрозрачная заливка и насыщенный текст того же цвета--ds-accent-light · --success-bg · --ds-warning-bg · --ds-danger-bg · --ds-info-bg · --bg-input

Пропы

ПропНазначение
toneneutral (по умолчанию) · accent · success · warning · danger · info
sizexs (8px) · sm (10px) · md (12px, по умолчанию)

Avatar ui-kit/Avatar.tsx

Кружок с эмодзи или фото ребёнка. Проп onAccent — полупрозрачно-белая заливка, когда аватар сидит на акцентной плашке. Не интерактивен.

sm / md / lg — обычный фон vs onAccent

default
👶
👶
👶
onAccent
👶
👶

sm=32px, md=40px, lg=56px.

Свойства

ЭлементЗначениеТокен
Размеры32 / 40 / 56pxh-8 · h-10 · h-14
Эмодзи внутри14 / 20 / 30px — по размеруfont-size/14 · font-size/20
Скруглениеполноеrounded-full
Заливкаакцентная подложка ребёнка; на акцентной плашке — полупрозрачно-белая--ds-accent-light · opacity/25

Пропы

ПропНазначение
sizesm · md (по умолчанию) · lg
emojiЭмодзи ребёнка
photoUrlФото вместо эмодзи
backgroundЗаливка кружка — акцентный цвет ребёнка
onAccentПолупрозрачно-белая заливка: аватар сидит на акцентной плашке

Field «поле формы» ui-kit/Field.tsx

Input и SelectField — одно и то же поле, отличается только способ ввода: руками или выбором из списка. Поверхность общая, но компоненты разные: Input наследует атрибуты <input> и отдаёт DOM-событие, SelectField — готовое значение.

Ручной ввод и выбор из списка

Отступ, радиус, обводка и высота у обеих поверхностей одинаковые.

Свойства

ЭлементЗначениеТокен
Высота поля44pxh-11
Отступ по горизонтали16pxpx-4 · space/16
Скругление10pxrounded-control
Обводка1px--border-card
Подпись12/500, отступ снизу 6pxButtonCaption · space/6

Что содержит

ЭкспортНазначение
FIELD_SURFACEСтрока классов поверхности: высота, отступы, радиус, обводка, кегль. Без w-full и фона — их добавляет конкретное поле
FieldОбёртка с подписью над контролом или группой контролов — например, «Дата рождения» над тремя списками или «Тип питания» над ChipSelect. Нет label — контрол возвращается как есть, без лишнего <div>

Input и Slider ui-kit/Input.tsx · ui-kit/Slider.tsx

Поле ввода (текст или число) и ползунок. Slider используется голым только там, где не нужна отдельная подпись — в остальных случаях он обёрнут в составной SliderField (см. «Составные»).

Input — с подписью, без, disabled

Фокус — кольцо 2px без зазора: у поля уже есть своя обводка.

Slider — обычный и disabled

Дорожка 6px. Бегунок и его фокус-кольцо стилизуются глобально в index.css.

Поле ввода — text input

16px
16px
Например, Маша
ВариантВертикальныйГоризонтальныйЗазорВысотаТекст
поле вводаpx-4 · 16pxh-11 · 44pxfont-size/14 · вес наследуется

Вертикального паддинга нет: текст центрируется высотой поля. Тот же набор классов дословно в трёх местах формы.

Свойства

ЭлементЗначениеТокен
Поле — высота44pxh-11
Поле — отступ по бокам16pxspace/16
Поле — текст14/400BodyPrimary
Поле — обводка и заливка1px, фон поляborder/card · --bg-input
Подпись над полем12/500, отступ снизу 6pxButtonCaption · space/6
Скругление10pxrounded-control
Дорожка ползунка6px, полное скруглениеh-1-5 · rounded-full
Кружок ползунка18×18px — вне шкалы space, инлайн CSS (::-webkit-slider-thumb/::-moz-range-thumb в index.css, не Tailwind-класс)--ds-accent
Фокус-кольцо кружка2px, с зазором 2px от кружка--ds-accent-border

Состояния

СостояниеЧто меняетсяТокен
defaultБазовый вид
focus-visibleКольцо 2px без зазора — у поля уже есть своя обводка. У ползунка кольцо на кружке, не на дорожке--ds-accent-border
disabledГаснет, курсор запретаopacity/60

Пропы

ПропНазначение
labelПодпись над полем — только у Input
valueЗначение
onChangeОбработчик ввода
placeholderПодсказка в пустом поле — только у Input
min / max / stepДиапазон — только у Slider
disabledЗаблокировано

ErrorNote ui-kit/ErrorNote.tsx

Сообщение об ошибке под формой — «Введите имя ребёнка», «Не удалось сохранить». Не интерактивен.

Единственный тон

Не удалось сохранить. Попробуйте ещё раз.

14/400, отступы 12 × 10px, радиус rounded-control. Заливка --ds-danger-bg, текст --ds-danger.

Свойства

ЭлементЗначениеТокен
Текст14/400BodyPrimary
Отступы12 × 10pxspace/12 · space/10
Скругление10pxrounded-control
Заливка и текстполупрозрачный danger--ds-danger-bg · --ds-danger
Тонодин — success/info/warning не заведены

Пропы

ПропНазначение
childrenТекст ошибки
classNameВнешний отступ — задаётся по месту

Skeleton ui-kit/Skeleton.tsx

Пульсирующая заглушка на время загрузки. Отдельно есть ListRowSkeleton — загрузочное состояние строки списка, живёт в одном файле с ней. Не интерактивен.

text / circle / block

Высота заглушки повторяет кегль: 14px под заголовок, 12px под подпись.

Свойства

ЭлементЗначениеТокен
Заливкафон карточки--bg-card
Анимацияпульсация, бесконечнаяanimate-pulse
Скругление4px text · полное circle · 10px blockrounded-xs · rounded-full · rounded-control
Высота текстовой строкипо кеглю: 14px заголовок, 12px подписьh-3-5 · h-3

Варианты

ВариантСкруглениеПод что
text4pxСтрока текста. Высота повторяет кегль: заголовок 14px, подпись 12px
circle999pxАватар, точка, трек переключателя
block10pxКарточка, кнопка, крупный блок — по умолчанию

Пропы

ПропНазначение
varianttext · circle · block (по умолчанию)
classNameРазмеры заглушки — задаются по месту

AccentPlate ui-kit/AccentPlate.tsx

Акцентная плашка: скруглённый блок с градиентом поверх акцентного цвета. Сам по себе — только фон и отступы, все состояния на элементах внутри.

Шапка страницы

👶 Маша

Самый крупный радиус в системе — rounded-plate, 28px.

Свойства

ЭлементЗначениеТокен
Скругление28pxrounded-plate
Отступы16px со всех сторонspace/16
Отступ снизу20pxspace/20
Заливкаградиент поверх акцентного цвета--accent-gradient

Пропы

ПропНазначение
childrenСодержимое плашки
classNameРаскладка внутри — задаётся по месту
ACCENT_PLATE_BGНе проп, а экспорт константы градиента — им же красится ChildSwitcherPopover, когда стоит не на плашке

PopoverPanel «поверхность выпадающего списка» ui-kit/PopoverPanel.tsx

Стилизованная поверхность попапа — рамка, фон, тень — поверх Radix PopoverContent. Содержимое передаётся снаружи. Примитив, а не составной: критерий — из чего собран сам, а не что импортирует.

Переключатель ребёнка — cmdk Command внутри

👶 Маша
👧 Соня

Список — cmdk Command/CommandItem, не голые кнопки (была своя логика выделения). Не тронуто — общая только рамка снаружи.

Выбор метрики графика — голые кнопки внутри

🍼 Кормлений
🥕 Прикорм скоро

Голые кнопки со своим скроллом и состояниями. Общая только рамка.

Пункт меню — попап выбора ребёнка

8px
8px
8px
8px
ВариантВертикальныйГоризонтальныйЗазорВысотаТекст
пункт попапаpy-2 · 8pxpx-2 · 8pxgap-2.5 · 10pxпо контентуfont-size/14 · вес наследуется

Компактнее строки настроек: 8px по горизонтали против 16px. Радиус свой — rounded-control-sm.

Свойства

ЭлементЗначениеТокен
Заливкафон карточки--bg-card
Обводка1px--border-card
Тень0 8px 24px rgba(0,0,0,.18)shadow/popover
Скругление10pxrounded-control

Пропы

ПропНазначение
align, sideOffsetПозиционирование — те же пропы Radix PopoverContent, компонент их только принимает и пробрасывает дальше
classNameВнутренние отступы/ширина — по месту (p-0 overflow-hidden w-56 у списка детей, p-1.5 w-56 у метрик)
childrenСодержимое — своё в каждом случае, компонент ничего не знает про список внутри

Составные11 компонентов

Собраны из нескольких элементов: повторяющиеся пилюли в сетке, несколько слотов под контент или прямой вызов другого компонента внутри (ListRowSkeleton, SettingsGroupListRow, TimeSelectSelectField).

ChipSelect «выбрать один из N» ui-kit/ChipSelect.tsx

Сетка кнопок-пилюль — выбор одного варианта из нескольких, активная опция залита акцентом. options/value/onChange, иконка в опции необязательна.

С иконкой — options[].icon задан

Иконка сверху, подпись снизу — FeedingSheet.tsx, EditEventModal.tsx, ChildProfileSection.tsx.

Без иконки — options[].icon не задан

Одна строка по центру, без верхнего слота под иконку.

compact — однострочный сегмент-контрол, PeriodSelector.tsx

32px, иконка инлайн перед подписью, а не сверху отдельным слотом.

Свойства

ЭлементЗначениеТокен
Подпись — обычный режим12/500ButtonCaption
Подпись — compact14/500BodyPrimary
Высота — минимум, не фикс44px обычный · 32px compact — тот же тап-таргет 44px, что у остальных интерактивных элементов (см. Layout)min-h-11 · min-h-8
Скругление10px обычный · 8px у пилюли внутри жёлобаrounded-control · rounded-control-sm
Зазор8px в сетке · 2px между пилюлями compactgap-2 · space/2
Заливка выбранногосплошной акцент, подпись белая--ds-accent
Заливка невыбранногофон поля · прозрачная в compact--bg-input
Обводканет ни в одном состоянииstroke/none

Состояния

СостояниеЧто меняетсяТокен
selectedАкцентная заливка, белая подпись--ds-accent
pressedПилюля гаснетopacity/70
focus-visibleКольцо 2px: с зазором в обычном режиме, внутрь — в compact, где пилюля сидит в жёлобе--ds-accent-border
disabledВся сетка гаснет; у отдельной опции compact гаснет только подпись — бейдж «скоро» остаётся читаемымopacity/50 · opacity/40

Пропы

ПропНазначение
options{ id, label, icon?, disabled?, badge? }[]
valueВыбранный id
onChangeОбработчик выбора
compactОднострочный сегмент-контрол 32px вместо сетки-пилюль 44px
columnsЧисло колонок сетки — по умолчанию по числу опций
disabledБлокирует всю сетку

QuickActionButton ui-kit/QuickActionButton.tsx

Плитка быстрой записи на Главной: крупная эмодзи над подписью, вся плитка — одна кнопка. Два «этажа»: primary — фиксированная пара «Кормление/Сон», secondary — настраиваемые. Не вариант Button: у того содержимое всегда в строку, здесь вертикальная плитка фиксированной высоты.

primary — Кормление / Сон

Вторая — в состоянии active: обводка и фон зелёные, эмодзи и подпись меняются. Disabled у этой пары не бывает.

secondary — настраиваемые и «Другое»

80px, обводка 1px нейтральная. Нереализованный пункт гаснет до 40%.

Состав кнопки — Быстрая запись

8px
8px
16px
16px
🍼
8px
Кормление
4px
4px
10px
10px
🧷
6px
Подгузник
ВариантВертикальныйГоризонтальныйЗазорВысотаТекст
primary16px8pxgap-2 · 8px96pxBodyPrimary
secondary10px4pxgap-1.5 · 6px80pxButtonCaption

Паддинг и высота заданы инлайном (padding: '16px 8px'), а не классами — единственный компонент вне шкалы Tailwind.

Свойства

ЭлементЗначениеТокен
Подпись14/500 у primary · 12/500 у secondaryBodyPrimary · ButtonCaption
Эмодзи26pxfont-size/26
Высота96px primary · 80px secondaryspace/96 · space/80
Обводка2px акцентная · 1px нейтральнаяstroke/bold · stroke/thin
Скругление10pxrounded-control
Зазор эмодзи ↔ подпись8px primary · 6px secondarygap-2 · gap-1-5
Заливкафон карточки; success при active--bg-card · --success-bg

Состояния

СостояниеЧто меняетсяТокен
defaultБазовый вид
activeЗапись уже идёт — обводка и заливка на success--success · --success-bg
pressedПлитка гаснетopacity/70
focus-visibleКольцо 2px с зазором--ds-accent-border
disabledГаснут эмодзи и подпись, бейдж «скоро» остаётсяopacity/40

Пропы

ПропНазначение
iconЭмодзи 26px над подписью
labelПодпись под иконкой
variantprimary — 96px, обводка 2px акцентная; secondary — 80px, 1px нейтральная (по умолчанию)
activeЗапись уже идёт — обводка и фон на success
disabledГасит иконку и подпись до 40%
badgeБейдж в правом верхнем углу — «скоро». Не гаснет вместе с плиткой

ListRow «строка списка» ui-kit/ListRow.tsx

Строка списка: иконка или аватар слева → заголовок и подпись → произвольный хвост справа. Самая частая форма в приложении.

Хвост-шеврон, хвост-время и слот leading

🌐
Язык
Русский
👶
Маша
11 мес.
🍼
Кормление
150 мл · Смесь
21:14 ✎

Кружок с заливкой — только у аватара в leading; иконка категории идёт без подложки.

Хвост-действие — шиты выбора и настройки списков

🍼
Кормление
+
🥕
Прикорм
скоро
🧷
Подгузник

Ручка перетаскивания — не хвост, а отдельный проп onDragStart. Погашенный пункт теряет яркость иконки и подписи, но не хвоста.

Строка списка — два паттерна: настройки и История

16px
16px
12px
12px
12px
12px

Второй паттерн — таймлайн событий в Истории:

12px
12px
ВариантВертикальныйГоризонтальныйЗазорВысотаТекст
строка настроекpy-3 · 12pxpx-4 · 16pxgap-3 · 12pxпо контентуBodyPrimary + BodySecondary
строка Историиpy-3 · 12px16px у родителяgap-3 · 12pxпо контентуfont-size/14 · weight/semibold + font-size/12

Две реализации одной роли: у строки настроек паддинг свой, у строки Истории 16px приходят от карточки-обёртки.

Свойства

ЭлементЗначениеТокен
Иконка категории18px в колонке 24pxtext-lg w-6
Аватар (leading)36px, кругrounded-full · фон --ds-accent-light
Заголовок14px / 500BodyPrimary
Заголовок, деструктивный14px / 500--ds-danger
Подпись и заметка12px, отступ сверху 2pxBodySecondary · space/2
Зазор иконка ↔ текст12px (в трёх шитах 8px)gap-3 / gap-2
Зазор внутри хвоста8pxgap-2
Скруглениенет — несёт контейнер группыrounded-control 10px + overflow-hidden
Разделитель1px--row-divider (Настройки, История) · --border-card (шиты)
Отступы12/16px, 12/0, min-h 44–64pxснаружи через className/style, по контексту (SettingsGroup, шит, карточка дня в Истории)
Высота строки44px без подписи, 62–63px с подписьюот контента

Состояния

СостояниеЧто меняетсяТокен
defaultБазовый вид
pressedВся строка гаснет до 60% (active:opacity-60; в Истории переопределено на 70%)opacity/60 · opacity/70
focus-visibleКольцо 2px внутрь границы, цвет --ds-accent-border. Именно inset: группа строк — overflow-hidden, кольцо с зазором обрезалось бы по краям карточки--ds-accent-border
dimmedИконка и текстовый блок до 40% — пункт «скоро». Хвост остаётся в полную силу, чтобы бейдж и Switch читалисьopacity/40
disabledcursor-not-allowed; работает только вместе с onClick
некликабельнаяБез onClick рендерится <div>, а не <button> — нет ни tap-фидбэка, ни фокуса

Пропы

ПропНазначение
iconЭмодзи в колонке 24px, без подложки — иконка категории
leadingПроизвольный узел вместо колонки-иконки — аватар. Приоритетнее icon
labelЗаголовок, text-sm/medium, обрезается многоточием
subtitleВторая строка под заголовком
footnoteТретья строка — заметка к записи в Истории
trailingХвост: шеврон, ✓, время, плюс, бейдж, Switch
onDragStartСтроку можно перетаскивать — в самом конце хвоста появляется ручка 44×44. Обычный колбэк: ListRow про framer-motion ничего не знает
dimmedГасит иконку и подписи до 40% — нереализованный пункт «скоро». Хвост не гасится
dangerКрасный заголовок — деструктивное действие
onClickДелает строку кнопкой: tap-фидбэк и фокус-кольцо ring-inset
disabledТолько вместе с onClick

PageHeader «шапка страницы» components/PageHeader.tsx

Шапка страницы: акцентная плашка, внутри переключатель ребёнка слева и действия справа, под ними необязательный контент. Лежит в components/, а не в ui-kit/ — знает про домен: ребёнка, Premium, возраст.

Строка — переключатель и действие

👶 Маша

История и Статистика: в плашке только строка.

С контентом под строкой

👶 Маша

Главная: под строкой статус-карточки. С контентом строка получает отступ снизу 16px.

Состояния

СостояниеЧто меняетсяТокен
defaultПереключатель и действия
isLoadingВместо переключателя скелетон 160×40 полупрозрачным белым — плашка уже акцентная, обычный серый скелетон на ней не читаетсяrgba(255,255,255,0.2)
без действийPaywall-ветка Статистики — только переключатель, строка не разъезжается

Пропы

ПропНазначение
activeChild · allChildrenДанные переключателя — какой ребёнок активен и между кем переключаться
isPremiumОт него зависит, доступно ли переключение между детьми
ageLabelВозраст под именем ребёнка
onSwitchВыбран другой ребёнок
isSwitchingПереключение уже идёт — переключатель показывает ожидание
isLoadingДанных ещё нет — вместо переключателя скелетон. На действия не влияет
actionsКнопки справа: тема, фильтр, экспорт
childrenКонтент под строкой, в той же плашке — статус-карточки Главной

BottomNav «нижняя навигация» components/BottomNav.tsx

Плавающая пилюля поверх контента, четыре фиксированных таба. Не уезжает со скроллом (position: fixed), поэтому отступ снизу отсчитывается от safe area. Пропов нет вовсе: табы зашиты константой tabs в самом файле. Ни disabled, ни loading — разделы доступны всегда.

Пилюля на экране

Инсет 20px — шире полей контента (16px). Ширина calc(100% − 40px), потолок 408px.

Активный таб и обычный

Активность дважды: цвет и strokeWidth 2.25 против 2. Иконка — квадрат-заглушка, чтобы разница в штрихе читалась.

Состав таба — отступы в масштабе 1:1

8px
8px
10px
10px
ВариантВертикальныйГоризонтальныйЗазорВысотаТекст
табpt-2.5 pb-2.5 · 10pxpx-2 · 8pxgap-1 · 4pxпо контентуTabLabel

Иконка 22px. Инсет 20px принадлежит пилюле, а не табу.

Свойства

ЭлементЗначениеТокен
ПоверхностьФон, обводка 1px, backdrop-filter: blur(20px)--nav-bg · --nav-border · stroke/thin
Скругление24pxrounded-panel
Тень0 4px 24px rgba(0,0,0,.25)shadow/nav
Ширинаcalc(100% − 40px), потолок 408px — инсет 20px с каждой стороны
Отступ снизуenv(safe-area-inset-bottom) + 10pxspace/10
Паддинг таба10px сверху и снизу, 8px по бокамspace/10 · space/8
Зазор иконка ↔ подпись4pxspace/4
Иконка22px, lucidew-5-5 · h-5-5
Подпись11px / 500, без интерлиньяжаTabLabel
Слойz-index 50 — выше контента, ниже шторок и модалок

Состояния

СостояниеЧто меняетсяТокен
defaultИконка и подпись вторичным цветом, штрих 2--text-secondary
activeТекущий раздел: цвет акцента и штрих 2.25 — единственное место в приложении, где состояние показано толщиной штриха, а не только цветом--ds-accent
pressedТаб гаснет до 60%opacity/60

Что содержит

ТабМаршрутИконка
Главная/dashboardHouse
История/historyClipboardList
Статистика/statsChartColumn
Настройки/settingsSettings

ReorderableRow «строка с перетаскиванием» components/ReorderableRow.tsx

Строка «выбранного» в шторках настройки списков: иконка → подпись → бейдж «скоро» → свич → ручка перетаскивания. Ручка не отдельный компонент, а часть анатомии строки — вобрана в ListRow колбэком onDragStart.

Выбранный пункт — реализованный и «скоро»

🧷
Подгузник
🥕
Прикорм
скоро

Тянуть можно только за ручку — остальная строка остаётся кликабельной.

Список в шторке — перетаскиваемая строка

16px
4px
8px
8px
ВариантВертикальныйГоризонтальныйЗазорВысотаТекст
ReorderableRowpy-2 · 8pxpl-4 16px · pr-1 4pxgap-2 · 8pxmin-h-16 · 64pxBodyPrimary

Асимметричный паддинг: 16px слева, 4px справа — место под ручку и Switch.

Свойства

ЭлементЗначениеТокен
Высота строки64pxmin-h-16
Отступы16px слева, 4px справа (под ручку), 8px сверху и снизуpl-4 pr-1 py-2
Тап-таргет ручки44 × 44px при иконке 18pxw-11 h-11 · --text-secondary
Фон и разделительзаливка карточки, 1px между строками--bg-card · --border-card

Состояния

СостояниеЧто меняетсяТокен
defaultКурсор «схватить» над ручкойcursor-grab
grabbingКурсор «тащу», пока строка перетаскиваетсяactive:cursor-grabbing
dimmedИконка и подпись до 40% — пункт «скоро». Бейдж, свич и ручка в полную силуopacity-40
focus-visibleКольцо 2px внутрь — ручка сидит в строке с обрезкой по краям--ds-accent-border

Пропы

ПропНазначение
item{ id, icon, label, implemented } — общая форма кандидата у всех трёх списков
isLastПоследняя в группе — без нижней границы, её рисует контейнер
onDisableСвич выключен — убрать пункт из выбранных

StatCard «карточка-показатель» ui-kit/StatCard.tsx

Подпись сверху → крупное число с единицей → вторичная строка → отбитый разделителем футер. Два слота под значением намеренно разные: secondary идёт без разделителя, footer — за ним. Сама карточка не интерактивна, она только показывает число.

Итог за период — Главная и Статистика

Кормление
5 раз
750 мл всего
2 ч 10 мин назад · 150 мл
Кормлений/день
6.0 раза
↓ -0.2vs прошлая

Слева Главная: итог за день и время последнего раза. Справа Статистика.

Нет данных и не реализовано

Вес
⚖️
Нет записей
скоро
Прикорм
🥕

Слева метрика живая, данных пока нет. Справа не реализована — гаснет до 40%.

Свойства

ЭлементЗначениеТокен
Подпись12px, отступ снизу 8pxBodySecondary · space/8
Значение24/700, интерлиньяж 1Number
Единица16/400text-base font-normal · --text-secondary (один сайт, не заведён как стиль)
Иконка вместо значения24px, 40%text-2xl opacity-40
Вторичная строка12px, отступ сверху 8pxBodySecondary · space/8
Футер12px, отступ и поля 8pxmt-2 pt-2 · разделитель --row-divider
Бейдж8px от верха и правого краяabsolute top-2 right-2
Отступы16px по кругуp-4
Скругление и фон10px, без обводкиrounded-control · --bg-card

Состояния

СостояниеЧто меняетсяТокен
defaultПодпись, значение с единицей, опционально вторичная строка и футер
нет данныхВместо значения приглушённая иконка, вторичная строка — «Нет записей». Подпись яркая: метрика живаяopacity-40 у иконки
dimmed («скоро»)Гаснет и подпись — метрика не реализована. Бейдж в углу остаётся в полную силуopacity-40 у подписи и иконки

Пропы

ПропНазначение
labelПодпись сверху — «Кормление», «Кормлений/день»
valueКрупное число. Не задано — вместо него рисуется icon
unitЕдиница рядом с числом — «раз», «мл», «ч». Мельче и не жирная: это не часть числа
iconЭмодзи вместо значения — «нет данных» или «не реализовано». Всегда приглушена
secondaryСтрока под значением, без разделителя: «0 мл всего», дельта к прошлому периоду
footerСтрока под разделителем: когда событие было в последний раз
badgeБейдж в правом верхнем углу — «скоро». Не гаснет вместе с карточкой
dimmedГасит подпись до 40% — метрика не реализована. У «нет записей» не ставится

SelectField и TimeSelect ui-kit/SelectField.tsx · ui-kit/TimeSelect.tsx

Выпадающий список — обёртка над shadcn Select, владеющая его оформлением. TimeSelect — время чч:мм двумя списками; нативный <input type="time"> не используется, потому что в Telegram WebView он рендерится по-разному на разных платформах.

Обычный список и его подпись

44px — та же высота, что у Input.

tone="card" + icon — шапка «Графики»

Постоянно активный контрол в шапке: обводка 2px акцентная, иконка внутри.

Свойства

ЭлементЗначениеТокен
Высота44px — как у Inputh-11
Текст14/400BodyPrimary
Отступ по бокам12pxspace/12
Обводка1pxstroke/thin · border/card
Скругление10pxrounded-control
Заливка триггерафон поля · фон карточки при tone="card"--bg-input · --bg-card
Выпадающий списокфон карточки, обводка 1px, тень--bg-card · shadow/popover
Пункт списка14/400, подсветка выбранного акцентом--ds-accent-light

Состояния

СостояниеЧто меняетсяТокен
defaultБазовый вид
openСписок раскрыт, шеврон повёрнут
focus-visibleКольцо 2px без зазора — как у Input--ds-accent-border
disabledГаснет, курсор запретаopacity/50

Пропы

ПропНазначение
options{ value, label }[]
labelПодпись над списком — та же, что у Input
iconИконка внутри триггера перед значением
toneinput — фон как у полей (по умолчанию); card — сплошная заливка для списка, стоящего прямо на странице
placeholder, disabledКак у обычного поля

SliderField «ползунок с подписью» ui-kit/SliderField.tsx

Обвязка вокруг Slider: подпись слева, текущее значение справа, ползунок, необязательная подсказка снизу. Не слит с Input и SelectField: у тех общая 44px-коробка с обводкой и фоном, у ползунка своей поверхности нет.

С подсказкой мин/макс — настройки ребёнка

Интервал кормления 3 ч 30 мин
1 ч8 ч

Подсказка — просто слот hint под ползунком; здесь это строка «от–до».

С текстовой подсказкой — объём кормления

Объём (мл) 120 мл
В пределах нормы для возраста

Тот же слот hint — здесь вместо диапазона динамический текст, цвет которого меняет вызывающий компонент (норма/ниже/выше).

Свойства

ЭлементЗначениеТокен
Подпись12/400, text-secondary--text-secondary
Значение14/700, text-primary--text-primary
Отступ строки подписи до ползунка6pxmb-1-5
Ползуноккак отдельный Slider

Пропы

ПропНазначение
labelПодпись слева над ползунком
valueLabelТекущее значение справа — отформатировано вызывающим (число + единица)
hintПроизвольный контент под ползунком — подсказка мин/макс или текст валидации. Несёт свой отступ сверху сам
...остальноеВсе пропы Slidermin/max/step/value/onChange/disabled

Sheet ui-kit/Sheet.tsx

Шторка снизу — основная форма всех модальных экранов: запись кормления, сна, подгузника, замеры, редактирование. Собрана из слотов Header, Body, Section и Footer. В проекте есть и ui/drawer.tsx поверх той же vaul, но его не импортирует ни один файл.

Header (заголовок + бейджи) → Body → Footer

Кормление · 09:30
Сегодня 🔒 Просмотр
🍼
150 мл
Смесь

Header.right — бейджи справа от заголовка. Footer — отдельный слот под Body, кнопка всегда на виду.

Section — подпись + контрол

Записать кормление
Тип кормления
🤱Левая
🤱Правая
🍼Смесь
Время

Один <Sheet.Section label="Тип кормления"> — подпись и контрол в одном слоте.

Список в шторке — переключаемая строка

16px
16px
ВариантВертикальныйГоризонтальныйЗазорВысотаТекст
строка-переключательpx-4 · 16pxgap-3 · 12pxmin-h-13 · 52pxBodyPrimary

Один и тот же класс px-4 min-h-13 в пяти файлах. gap-3 приходит из базового класса ListRow.

Свойства

ЭлементЗначениеТокен
Скругление верха20pxrounded-t-sheet
Заливкафон карточки--bg-card
Затемнение под шторкойчёрный 50%opacity/50
Ручка-индикатор48 × 6px, отступы 12 сверху и 16 снизуh-1-5 · space/12 · space/16
Цвет ручкиобводка карточки--border-card
Body — отступы по бокам/снизу20px / 24pxpx-5 pb-6
Footer — граница сверху, отступы1px, 20px/12px/20px--border-card · px-5 pt-3 pb-5

Состояния

СостояниеЧто меняетсяТокен
open / closedВыезд снизу и затемнение фонаopacity/50
swipeТянется пальцем за ручку и закрывается свайпом вниз

Пропы

СлотПропНазначение
Sheetopen, onCloseСостояние шторки
SheetmaxHeightRatioПотолок высоты как доля вьюпорта, по умолчанию 0.8. Формы в несколько шагов просят больше
SheetcontainerПортал-контейнер — только для изолированного показа в галерее компонентов
HeaderchildrenТекст заголовка
HeadersubtitleСтрока под заголовком — переключает нижний отступ (mb-1 вместо mb-5)
HeaderrightБейджи/дата справа от заголовка
BodychildrenЕдинственный обязательный слот — всё, что не Footer
SectionlabelНеобязательна — таймер/слайдер иногда сами несут подпись внутри
FooterchildrenКнопки — закреплены вне скролла, с верхней границей

Иконки

В приложении два набора, и это разные вещи. Векторные иконки — lucide-react, ими нарисован интерфейс: навигация, действия, стрелки, состояния. Эмодзи иконками только выглядят: они приходят из данных — тип события, аватар ребёнка — и живут по правилам текста, а не SVG.

lucide-react 25 иконок

Единственный источник векторных иконок, своих SVG в проекте нет. Иконки ниже нарисованы теми же path, что и в приложении — вытащены из node_modules/lucide-react, а не перерисованы.

Навигация

HouseТаб «Главная»
ClipboardListТаб «История»
ChartColumnТаб «Статистика»
SettingsТаб «Настройки», иконка приложения в App

Раскрытие и перелистывание

ChevronDownРаскрыть попап, выпадающий список, фильтр
ChevronUpСвернуть раскрытый список
ChevronLeftПредыдущая страница Истории
ChevronRightСледующая страница, хвост-шеврон строки

Действия

PencilИзменить запись — Главная и Статистика
EditИзменить — История и Настройки. Другая иконка, чем Pencil, при том же действии
PlusДобавить событие
XЗакрыть модалку
RefreshCwСбросить настройки ребёнка
DownloadВыгрузить данные Статистики
Share2Поделиться — приглашение второго родителя
ExternalLinkВнешняя ссылка — Premium и Настройки
ListFilterФильтр Истории
GitCompareСравнение периодов в Статистике
GripVerticalРучка перетаскивания строки

Состояние

CheckВыбранный пункт, галка в списке
Loader2Спиннер внутри кнопки

Данные и тема

BarChart3Переключатель вида графика — столбцы. Тот же глиф, что ChartColumn: BarChart3 — устаревший алиас
LineChartПереключатель вида графика — линия
SunСветлая тема
MoonТёмная тема

Две находки при сверке с кодом. BarChart3 и ChartColumn — один и тот же глиф: первое имя в lucide устаревшее, но в проекте импортируются оба, в разных файлах. И наоборот: действие «Изменить» нарисовано двумя разными иконками — Pencil на Главной и в Статистике, Edit (это square-pen, карандаш в рамке) в Истории и Настройках. Ни то, ни другое не решение — просто так сложилось.

Размеры

Размер задаётся пропом size поштучно, общего токена «размер иконки» нет. Шесть значений на 42 места в коде — считая дефолтные 24px там, где проп просто забыли задать.

ОбразецЗначениеРеализацияГде используется
14pxsize={14}Инлайн рядом с текстом — галка выбора, «Изменить», внешняя ссылка. 13 мест
16pxsize={16}Самый частый: шевроны пагинации, карандаш редактирования, спиннер кнопки. 16 мест
18pxsize={18}Иконка-действие в шапке и шторках — фильтр, закрыть, добавить, ручка перетаскивания. 5 мест
20pxsize={20}Только переключатель темы на Главной — Sun и Moon
22pxsize={22}Только нижняя навигация, все четыре таба (BottomNav)
24pxпроп не заданДефолт lucide, всплывает там, где размер забыли задать: библиотечный ui/select.tsx и иконка приложения в App.tsx

Штрих и цвет

Толщина штриха везде 2 — дефолт lucide, никто его не переопределяет. Единственное исключение — активный таб навигации: strokeWidth 2.25, чтобы состояние читалось не только цветом. Цвет иконка не задаёт вовсе: lucide рендерит stroke="currentColor", поэтому иконка всегда того же цвета, что текст рядом — --text-secondary в спокойном состоянии, --ds-accent в активном.

Эмодзи 11 размеров

Это не иконки и не часть набора: эмодзи приходят из данных (тип события, аватар ребёнка) или стоят заглушкой в пустом состоянии. Отсюда и разнобой размеров — у каждого свой, потому что это текст внутри своего компонента, а не элемент шкалы. Переиспользуется как «стандартная иконка строки списка» только 18px; остальные размеры под одно-два места и токенами не являются.

12px📝Бейджи типов событий в сводке дня, префикс заметки
14px💎Инлайн-префикс текста — «Только для Premium»
16px👤Фолбэк-аватар совместного доступа (нет фото)
18px🍼Строка списка в шторке — самый частый размер6+ мест
20px🌙Плашка статуса на Главной — «Следующий сон»
22px🥕Карточка нереализованного показателя («скоро»)
24px⚖️Пустое состояние карточки измерений
26px🍼Кнопки «Быстрая запись» на Главной
30px👶Крупный аватар ребёнка (ChildCard)
36px⚠️Пустое состояние экрана
48px📱Экран блокировки поворота, крупные заглушки

Размер задан теми же классами, что и текст: 12–16px — text-xs/text-sm/text-base, 18–48px — text-lgtext-5xl, кроме 22px и 26px, у которых инлайновый style={{ fontSize: N }}. То есть эмодзи размечены как типографика, а не как иконки — что и объясняет, почему их размеры не сходятся со шкалой lucide.

Figma ↔ Код

Что вошло в выборку. Весь mamkin-mini-app/src, кроме библиотечных components/ui/* (shadcn/Radix, их не трогали) и служебной галереи pages/dev/*. Публичный лендинг LandingPage.tsx тоже вне шкал: он вне Telegram Mini App и размеры там свои, некратные (1.75rem, 1.8rem, 3rem). Отдельные значения из библиотечных файлов в таблицах всё же есть — они помечены в колонке «Где используется».

Всё, что нужно для переноса в Figma Variables, в одном месте — код-токен, примитив/переменная Figma, значение. Без «см. выше»: цвета, типографика, spacing, opacity и border — полностью, без сокращений.

Словарь колонок один на все справочные таблицы страницы: Образец — как токен выглядит на самом деле, Primitive — имя примитива или ссылка на него, Semantic — именованная переменная Figma, ссылающаяся на примитив, CSS-переменная — что от этого есть в коде (бывает только у семантического тира), Значение — разрешённое значение, Реализация — механизм в коде там, где он не сводится к примитиву, Где используется — места в интерфейсе. В таблицах gap/height/width/margin/padding имя переменной приведено к единому виду для Figma и не всегда 1:1 с классом в коде: дробные значения через дефис (h-1-5 вместо h-1.5 — точка не годится для имени переменной), max-h/max-w без приставки (h-44/w-48), margin/padding без направления (m-N/p-N вместо mb-N/px-N). Настоящий класс из кода в таких случаях — в колонке «Где используется». Таблицы разбора компонентов ниже устроены иначе: там строка — это элемент, состояние или проп, а не токен.

Типографика — Text Styles

Типографическая настройка в Figma — это Text Style: именованный набор Family + Size + Weight + Line height + Letter spacing, применяется одним кликом. Только стили, которые реально повторяются в коде, с готовым line-height под свой размер.

В Figma Text Style к переменной привязаны family (всегда Typography/font-family/base), weight и size — все трое через примитив. Не биндятся вообще (ограничение Figma, не наше решение) — line-height и letter-spacing: всегда сырое число внутри стиля, в процентах. В коде у типографики, в отличие от цвета, вообще нет CSS-переменной — ни одной --font-* в index.css. Реализация — сырые Tailwind-классы прямо в JSX; какие именно и где применяется каждый стиль — в разделе Типографика.

SemanticРазмер / весLine-heightLetter-spacing
Numberfont-size/24 · weight/bold100%0%
PageTitlefont-size/20 · weight/bold140%0%
ModalTitlefont-size/18 · weight/bold156% (125% у имени ребёнка — не отдельный стиль)0%
SheetHeaderfont-size/16 · weight/semibold150%0%
TileValuefont-size/14 · weight/semibold125%0%
BodyPrimaryfont-size/14 · weight/medium143%0%
Bodyfont-size/14 · weight/regular143%0%
AccentCaptionfont-size/12 · weight/semibold133%0%
ButtonCaptionfont-size/12 · weight/medium133%0%
BodySecondaryfont-size/12 · weight/regular133%0%
SectionLabelfont-size/11 · weight/semiboldAuto5%
TabLabelfont-size/11 · weight/medium100%0%
SmallBadgefont-size/10 · weight/mediumAuto0%
MicroBadgefont-size/8 · weight/medium100%0%

Line-height «Auto» — код не переопределяет line-height (наследуется от шрифта), в Figma это отдельный режим поля, не число. 5% letter-spacing у SectionLabel — то же самое, что 0.05em в коде (Figma-проценты у трекинга считаются от font-size, как и em).

Цвет — Primitives

Neutrals — тёмная и светлая шкала отдельно (не совпадают 1:1, поэтому разные примитивы, не тема одного).

Neutrals — Dark

ОбразецPrimitiveЗначение
color/white#ffffff
color/white 8%#FFFFFF 8%
color/black#000000
color/dark/500#8a8a8e
color/dark/400#555555
color/dark/600#333333
color/dark/700#2e2e2e
color/dark/800#252525
color/dark/750#1e1e1e
color/dark/900#111111

Neutrals — Light

ОбразецPrimitiveЗначение
color/white#ffffff
color/black#000000
color/black 5%#000000 5%
color/gray/50#f9f9f9
color/gray/100#f2f2f7
color/gray/150#f0f0f0
color/gray/200#e5e7eb
color/gray/400#9ca3af
color/gray/500#6b7280
color/gray/900#111111

Цвета — по оттенку

ОбразецPrimitiveЗначение
color/green/400#30D158
color/green/400 15%#30D158 15%
color/green/500#34C759
color/green/500 12%#34C759 12%
color/green/600#1D9E75
color/green/600 15%#1D9E75 15%
color/green/600 40%#1D9E75 40%
color/blue/300#64D2FF
color/blue/500#0A84FF
color/blue/500 15%#0A84FF 15%
color/blue/500 40%#0A84FF 40%
color/blue/600#007AFF
color/blue/600 12%#007AFF 12%
color/amber/300#FFB340
color/amber/500#FF9F0A
color/amber/500 15%#FF9F0A 15%
color/amber/600#FF9500
color/amber/600 12%#FF9500 12%
color/amber/600 15%#FF9500 15%
color/amber/600 40%#FF9500 40%
color/red/500#FF453A
color/red/500 15%#FF453A 15%
color/red/600#FF3B30
color/red/600 12%#FF3B30 12%
color/purple/300#A78BFA
color/purple/500#7B72E9
color/purple/500 15%#7B72E9 15%
color/purple/500 40%#7B72E9 40%
color/rose/300#FF6B8A
color/rose/500#FF375F
color/rose/500 15%#FF375F 15%
color/rose/500 40%#FF375F 40%
color/teal/300#5AC8FA
color/teal/500#32ADE6
color/teal/500 15%#32ADE6 15%
color/teal/500 40%#32ADE6 40%
color/neutral-accent/300#AEAEB2
color/neutral-accent/500#8E8E93
color/neutral-accent/500 15%#8E8E93 15%
color/neutral-accent/500 40%#8E8E93 40%

Строки с процентом (напр. color/green/400 15%) — отдельные primitive с прозрачностью, запечённой в HEX/rgba, не наложение opacity поверх основного цвета: Figma не даёт применить Fill Opacity к уже вставленной ссылке на переменную, только к слою целиком.

Цвет — Semantic

CSS-переменная в коде · Semantic (Figma) · Primitive, на который ссылается · где используется. Разрешённое значение цвета — у самого примитива в таблице выше, здесь не повторяется.

Light mode

CSS-переменнаяSemanticPrimitiveГде используется
--bg-pagebg/pagecolor/gray/100Фон страницы
--bg-cardbg/cardcolor/whiteФон карточки/шита
--bg-card2bg/card2color/gray/50Фон вложенной группы (settings)
--bg-inputbg/inputcolor/gray/100Фон полей ввода, неактивные кнопки
--border-cardborder/cardcolor/gray/200Обводка карточек/полей
--text-primarytext/primarycolor/gray/900Основной текст
--text-secondarytext/secondarycolor/gray/500Вторичный текст
--text-placeholdertext/placeholdercolor/gray/400Плейсхолдеры
--row-dividerrow/dividercolor/gray/150Разделители строк списков
--nav-bgnav/bgcolor/whiteФон нижней навигации
--nav-bordernav/bordercolor/gray/200Обводка нижней навигации
--icon-btn-bgicon-btn/bgcolor/black 5%Фон Button variant=subtle

Dark mode

CSS-переменнаяSemanticPrimitiveГде используется
--bg-pagebg/pagecolor/dark/900Фон страницы
--bg-cardbg/cardcolor/dark/800Фон карточки/шита
--bg-card2bg/card2color/dark/750Фон вложенной группы (settings)
--bg-inputbg/inputcolor/dark/700Фон полей ввода, неактивные кнопки
--border-cardborder/cardcolor/dark/600Обводка карточек/полей
--text-primarytext/primarycolor/whiteОсновной текст
--text-secondarytext/secondarycolor/dark/500Вторичный текст
--text-placeholdertext/placeholdercolor/dark/400Плейсхолдеры
--row-dividerrow/dividercolor/dark/600Разделители строк списков
--nav-bgnav/bgcolor/dark/750Фон нижней навигации
--nav-bordernav/bordercolor/dark/600Обводка нижней навигации
--icon-btn-bgicon-btn/bgcolor/white 8%Фон Button variant=subtle

Status & CTA

Один токен — одна строка. accent/accent-light сюда не входят — дубль варианта default из Accent ребёнка ниже. -bg — тот же primitive, что у основного токена, с прозрачностью.

Dark

CSS-переменнаяSemanticPrimitiveГде используется
--successsuccesscolor/green/400Подтверждение, идёт/активно
--success-bgsuccess-bgcolor/green/400 15%Подтверждение, идёт/активно (фон)
--ds-warningwarningcolor/amber/500Предупреждение, error-баннер Dashboard
--ds-warning-bgwarning-bgcolor/amber/500 15%Предупреждение, error-баннер Dashboard (фон)
--ds-dangerdangercolor/red/500Удаление, ошибки ввода
--ds-danger-bgdanger-bgcolor/red/500 15%Удаление, ошибки ввода (фон)
--ds-infoinfocolor/blue/500Кормление в EventRow/history
--ds-info-bginfo-bgcolor/blue/500 15%Кормление в EventRow/history (фон)

Light

CSS-переменнаяSemanticPrimitiveГде используется
--successsuccesscolor/green/500Подтверждение, идёт/активно
--success-bgsuccess-bgcolor/green/500 12%Подтверждение, идёт/активно (фон)
--ds-warningwarningcolor/amber/600Предупреждение, error-баннер Dashboard
--ds-warning-bgwarning-bgcolor/amber/600 12%Предупреждение, error-баннер Dashboard (фон)
--ds-dangerdangercolor/red/600Удаление, ошибки ввода
--ds-danger-bgdanger-bgcolor/red/600 12%Удаление, ошибки ввода (фон)
--ds-infoinfocolor/blue/600Кормление в EventRow/history
--ds-info-bginfo-bgcolor/blue/600 12%Кормление в EventRow/history (фон)

Accent ребёнка (7 вариантов × 4 роли)

Modes на бесплатном тарифе Figma нет — вместо одной переменной с 7 режимами здесь 28 отдельных Semantic-переменных (по 4 на цвет: сам акцент / подложка 15% / текст на подложке / обводка 40%). Смена акцента у ребёнка — это выбор готового набора, не один клик по переменной.

CSS-переменнаяSemanticPrimitiveГде используется
--ds-accentaccent/defaultcolor/neutral-accent/500Цвет CTA/аватара, пока ребёнку не выбрали свой
--ds-accent-lightaccent-light/defaultcolor/neutral-accent/500 15%Подложка под accent (default)
--accent-textaccent-text/defaultcolor/neutral-accent/300Текст поверх accent-light (default)
--ds-accent-borderaccent-border/defaultcolor/neutral-accent/500 40%Обводка инфо-блока/плашки (default)
--ds-accentaccent/purplecolor/purple/500Цвет CTA/аватара — вариант «Фиолетовый»
--ds-accent-lightaccent-light/purplecolor/purple/500 15%Подложка под accent (Фиолетовый)
--accent-textaccent-text/purplecolor/purple/300Текст поверх accent-light (Фиолетовый)
--ds-accent-borderaccent-border/purplecolor/purple/500 40%Обводка инфо-блока (Фиолетовый)
--ds-accentaccent/bluecolor/blue/500Цвет CTA/аватара — вариант «Синий»
--ds-accent-lightaccent-light/bluecolor/blue/500 15%Подложка под accent (Синий)
--accent-textaccent-text/bluecolor/blue/300Текст поверх accent-light (Синий)
--ds-accent-borderaccent-border/bluecolor/blue/500 40%Обводка инфо-блока (Синий)
--ds-accentaccent/greencolor/green/600Цвет CTA/аватара — вариант «Зелёный»
--ds-accent-lightaccent-light/greencolor/green/600 15%Подложка под accent (Зелёный)
--accent-textaccent-text/greencolor/green/400Текст поверх accent-light (Зелёный)
--ds-accent-borderaccent-border/greencolor/green/600 40%Обводка инфо-блока (Зелёный)
--ds-accentaccent/pinkcolor/rose/500Цвет CTA/аватара — вариант «Розовый»
--ds-accent-lightaccent-light/pinkcolor/rose/500 15%Подложка под accent (Розовый)
--accent-textaccent-text/pinkcolor/rose/300Текст поверх accent-light (Розовый)
--ds-accent-borderaccent-border/pinkcolor/rose/500 40%Обводка инфо-блока (Розовый)
--ds-accentaccent/orangecolor/amber/600Цвет CTA/аватара — вариант «Оранжевый»
--ds-accent-lightaccent-light/orangecolor/amber/600 15%Подложка под accent (Оранжевый)
--accent-textaccent-text/orangecolor/amber/300Текст поверх accent-light (Оранжевый)
--ds-accent-borderaccent-border/orangecolor/amber/600 40%Обводка инфо-блока (Оранжевый)
--ds-accentaccent/tealcolor/teal/500Цвет CTA/аватара — вариант «Бирюзовый»
--ds-accent-lightaccent-light/tealcolor/teal/500 15%Подложка под accent (Бирюзовый)
--accent-textaccent-text/tealcolor/teal/300Текст поверх accent-light (Бирюзовый)
--ds-accent-borderaccent-border/tealcolor/teal/500 40%Обводка инфо-блока (Бирюзовый)

--accent-gradient (7 вариантов, 135°, opacity/30 → opacity/15 поверх того же primitive) — Opacity-приёмом не сделать, нужен отдельный Gradient-стиль в Figma, не Number+Color.

Пространство, прозрачность, обводка

Один примитив (space/N), но каждое из семи свойств — свои именованные переменные Figma, каждая со своей ссылкой на примитив (как rounded-control ниже ссылается на space/10). Токен из кода = имя переменной Figma 1:1.

Про колонку «CSS-переменная». Она стоит только у семантического тира — как и у цвета; в таблице примитивов ниже её нет, потому что примитив в коде вообще не существует отдельной переменной. В отличие от цвета, где на каждый семантический токен есть своя --*, здесь настоящая CSS-переменная существует только у скруглений (--radius-*) и у прозрачностей, запечённых в цветовые переменные (--ds-accent-light, --success-bg и т.п.). У gap / height / width / margin / padding и у толщины обводки переменных нет вообще, и прочерк в колонке — не пропуск в документации, а как есть: Tailwind 3 генерирует эти классы статически на этапе сборки, значение подставляется прямо в CSS-правило и во время исполнения ничего не читается. Единая база --space-unit: 0.25rem в index.css объявлена, но реально связывает только шкалу --radius-*, где каждое значение — её множитель; со шкалой Tailwind она совпадает численно, но не связана.

PrimitiveЗначение
space/00px
space/22px
space/44px
space/66px
space/88px
space/1010px
space/1212px
space/1414px
space/1616px
space/2020px
space/2222px
space/2424px
space/2828px
space/3232px
space/3636px
space/4040px
space/4444px
space/4848px
space/5252px
space/5656px
space/6464px
space/6868px
space/7272px
space/8080px
space/9696px
space/112112px
space/128128px
space/144144px
space/160160px
space/176176px
space/192192px
space/224224px
space/448448px

corner radius 8 значений

CSS-переменнаяSemanticPrimitiveГде используется
rounded-nonespace/0Только в библиотечном компоненте, в интерфейсе приложения не встречается
--radius-xsrounded-xsspace/4Мелкие декоративные скругления
--radius-control-smrounded-control-smspace/8Вложенные элементы внутри карточек — пункты списка при hover/active
--radius-controlrounded-controlspace/10Базовый радиус — карточки, кнопки, инпуты, попапы по всему приложению
--radius-sheetrounded-sheetspace/20Верх bottom-sheet. В коде применяется направленным вариантом rounded-t-sheet — скруглены только верхние углы, нижние уходят за край экрана
--radius-panelrounded-panelspace/24Крупные панели — нижняя навигация, шторка выбора ребёнка
--radius-platerounded-platespace/28Акцентная плашка-шапка — Главная, История, Статистика
rounded-fullАватары, точки-индикаторы, круглые кнопки, переключатели — не шаг сетки, keyword-значение

item spacing (gap) 8 значений

CSS-переменнаяSemanticPrimitiveГде используется
gap-0-5space/2Зазор между пилюлями в сегмент-контроле ChipSelect compact — единственное место
gap-1space/4Минимальный отступ между мелкими элементами — иконка и текст рядом
gap-1-5space/6Отступ между элементами строки настроек
gap-2space/8Стандартный отступ внутри блока — самое частое значение
gap-2-5space/10Отступ между строками карточки
gap-3space/12Отступ между карточками
gap-4space/16Отступ между секциями страницы
gap-5space/20Одно место — промо-страница Premium, и там это space-y-5: отступ между детьми, а не gap флексбокса

height 26 значений

CSS-переменнаяSemanticPrimitiveГде используется
h-1-5space/6Ручка-индикатор шторки (единообразно во всех шторках), трек слайдеров в настройках ребёнка
h-2space/8Точки-индикаторы статуса, высота полоски в skeleton
h-3space/12Skeleton текстовых плейсхолдеров
h-3-5space/14Skeleton текстовых плейсхолдеров (вариант длиннее строки)
h-4space/16Разделительная вертикальная линия между элементами
h-5space/20Skeleton тогла в настройках уведомлений, иконка Premium-фичи
h-5-5space/22Skeleton-иконка нижней навигации (парно с width — тот же элемент)
h-6space/24Skeleton-заголовки, компактные круглые кнопки/аватары (size=sm)
h-7space/28Skeleton-заголовок; бегунок переключателя
h-8space/32Компактные кнопки/бейджи; трек переключателя
h-9space/36Круглые аватары/иконки среднего размера (карточки настроек, приглашения)
h-10space/40Аватары среднего размера, инпуты
h-11space/44Минимальная высота тап-таргета (Apple HIG) — самое частое значение: инпуты, кнопки, строки попапов
h-13space/52Строки настроек с крупной иконкой, крупная кнопка
h-14space/56Крупные круглые аватары (карточка ребёнка), skeleton-плашка блока статистики
h-16space/64Строки настроек с превью-иконкой карточки
h-17space/68Skeleton статус-строк «Следующее кормление/Следующий сон» в акцентной плашке-шапке
h-18space/72Запас снизу под sticky-бар «Сохранить/Отменить»
h-20space/80Переиспользуемый размер skeleton-карточек — блоки быстрой записи/статистики
h-24space/96Переиспользуемый размер skeleton-карточек по всему приложению
h-36space/144Нейтральная заглушка при переключении вкладок (не форма конкретного экрана)
h-40space/160Placeholder-блок графика в Истории
h-44space/176Ограничение высоты списка в попапе (Статистика)
h-48space/192Skeleton графика (Статистика)
h-fullНа всю высоту родителя (изображения аватаров)
h-screenКорневой контейнер приложения; потолок высоты шторок и модалок

width 23 значения

CSS-переменнаяSemanticPrimitiveГде используется
w-2space/8Точки-индикаторы статуса (парно с height)
w-5space/20Skeleton тогла в настройках уведомлений (парно с height, тот же элемент), иконка Premium-фичи
w-5-5space/22Skeleton-иконка нижней навигации — совпадает с реальным размером иконок. Не на 4px-сетке — совпадение, не шаг ритма
w-6space/24Контейнер эмодзи-иконки в строке настроек (единообразно во всех шторках), компактные круглые кнопки
w-8space/32Компактные круглые аватары/иконки (size=xs/sm)
w-9space/36Круглые аватары/skeleton тогла среднего размера
w-10space/40Аватары среднего размера
w-11space/44Крупные квадратные иконки/drag-хендлы настроек
w-12space/48Ручка-индикатор шторки (парно с height, единообразно во всех шторках)
w-13space/52Трек переключателя
w-14space/56Крупные круглые аватары
w-16space/64Skeleton текстовых плейсхолдеров
w-20space/80Skeleton текстовых плейсхолдеров
w-24space/96Skeleton текстовых плейсхолдеров
w-28space/112Skeleton текстового плейсхолдера (доступ к профилю)
w-32space/128Skeleton заголовков/строк
w-40space/160Skeleton заголовков
w-44space/176Skeleton заголовков (вариант длиннее)
w-48space/192Ограничение ширины второй skeleton-строки (уведомления) — потолок, не жёсткая ширина
w-56space/224Ширина всплывающей панели — переключатель ребёнка, сравнение периодов в Статистике
w-mdspace/448Максимальная ширина мобильного контейнера приложения
w-fullНа всю ширину родителя — самое частое значение
w-autoАвтоматическая ширина (кнопка сравнения периодов в Статистике)

margin 12 значений

CSS-переменнаяSemanticPrimitiveГде используется
m-0space/0Сброс отступа у последнего элемента списка
m-0-5space/2Минимальный зазор, подпись под заголовком строки
m-1space/4Отступ между тесно связанными строками (лейбл/значение)
m-1-5space/6Подпись поля перед инпутом
m-2space/8Пояснительный текст под группой контролов
m-2-5space/10Отступ под сеткой быстрых кнопок
m-3space/12Отступ между карточками, второе по частоте после 16px
m-4space/16Отступ между блоками, самое частое margin-значение в коде
m-5space/20Отступ между секциями страницы, см. rhythm/section-gap ниже
m-6space/24Отступ перед списком опций в шторке; отступ под заголовком страницы
m-8space/32Отступ в самом низу страницы Настроек, перед версией приложения
m-24space/96Только в библиотечном компоненте (шторка снизу), не в интерфейсе приложения

padding 14 значений

CSS-переменнаяSemanticPrimitiveГде используется
p-0space/0Явный сброс паддинга у переиспользуемого компонента
p-0-5space/2Микро-паддинг вокруг мелких бейджей/иконок нижней навигации
p-1space/4Паддинг компактных иконочных кнопок-«карандашей»
p-1-5space/6Паддинг маленьких бейджей/чипов
p-2space/8Паддинг компактных кнопок/строк-табов нижней навигации
p-2-5space/10Паддинг средних кнопок/строк табов нижней навигации
p-3space/12Паддинг строки настроек — самое частое значение после 16px
p-3-5space/14Паддинг чуть крупнее стандартной строки
p-4space/16Самый частый вариант — то же самое, что layout/margin ниже (поля страницы)
p-5space/20Паддинг шторок (bottom sheet) по бокам
p-6space/24Паддинг крупных модалок
p-8space/32Запас снизу под safe-area/sticky-бар в шторках (единообразно во многих модалках)
p-10space/40Отступ под иконку слева; запас снизу в отдельных модалках
p-16space/64Пустое состояние (empty state) по центру страницы История/Статистика

Opacity и толщина обводки — по строению это примитивы (сырое число, ни на что не ссылается), но в Figma-файле их пока нет вообще: вбиваются вручную в каждом использовании. Семантического тира над ними тоже нет — поэтому колонки «Semantic» здесь не бывает, а «Реализация» показывает механизм в коде. rounded-full уже показан выше, в таблице corner radius — отдельной таблицы под него не заводим. Колонка «CSS-переменная» у opacity — не переменная прозрачности как таковой: своей --opacity-* в проекте нет, процент запечён внутрь цветовой переменной (--ds-accent-light — это акцент, уже смешанный с 15%). Где прочерк — значение живёт классом Tailwind или инлайн-стилем, в CSS-переменную не вынесено.

Opacity

CSS-переменнаяPrimitiveЗначениеРеализацияГде используется
opacity/00rgba(...,0), inlineВторой (конечный) стоп градиента-маски плашки-шапки (AccentPlate.tsx)
--icon-btn-bgopacity/55запечено в переменной, светлая темаФон Button variant=subtle, светлая тема
--icon-btn-bgopacity/88запечено в переменной (тёмная) + отдельный inline rgbaФон Button variant=subtle тёмная тема; отдельно — hover-курсор графиков в Статистике (совпадение процента, не общий токен)
opacity/1010Не используется в реальном интерфейсе — только пример в dev/Showcase; в проде объединено в --ds-danger-bg (0.12/0.15)
--success-bgopacity/1212запечено в переменной, светлая темаФон статус-плашек, светлая тема — по одной переменной на статус: --success-bg, --ds-warning-bg, --ds-danger-bg, --ds-info-bg
--success-bg · --ds-accent-lightopacity/1515запечено в переменныхФон статус-плашек тёмная тема (те же 4 переменные); фон --ds-accent-light — все 7 акцентных цветов
opacity/1818rgba(255,255,255,.18), inlineПервый стоп градиента плашки-шапки; полупрозрачные подложки на ней (Dashboard.tsx)
opacity/2020rgba(255,255,255,.2), inline ×6Overlay-подложка под skeleton загрузки — не общая переменная, значение вручную скопировано в 6 файлах
opacity/2525rgba, inlineТень нижней навигации (BottomNav); отдельно — подложка аватара на акцентном фоне (Avatar onAccent)
opacity/3030rgba(255,255,255,.3), inlineОбводка hero-карточки на Главной (Dashboard); бегунок Switch сюда не относится — у него Tailwind shadow-lg, другой механизм
--ds-accent-borderopacity/4040смешанно: класс opacity-40, inline-style и запечено в переменнойИконки нереализованных функций и disabled-пагинация Истории (класс), disabled-опции в шторках (inline), кольцо фокуса и обводки на акценте (--ds-accent-border, все 7 цветов)
opacity/5050disabled:opacity-50Disabled-состояние кнопок — вторая по частоте; у primary/danger кнопок гашение иначе (--bg-input, без opacity)
opacity/6060active:/disabled:opacity-60, отдельно inline rgbaActive/tap и disabled-состояния строк настроек (класс); тёмный скрим модалок — отдельный inline rgba(0,0,0,.6)
opacity/7070active:opacity-70Active/tap-нажатие — большинство кнопок приложения, самое частое; единственное значение с одной реализацией везде
opacity/7575JS-константа rgbaВторичный текст на цветной hero-карточке (Dashboard, ChildSwitcherPopover)
opacity/9292opacity-[92%]Paywall-баннер «Ранняя история» — единственное место, Tailwind arbitrary value
opacity/100100inline style, ветка «иначе»Явная отмена унаследованной прозрачности — всегда «иначе» в тернарнике рядом с другим opacity-значением, не отдельный класс

stroke — толщина обводки

CSS-переменнаяPrimitiveЗначениеРеализацияГде используется
stroke/none0pxborder-0 / border-transparentДва разных паттерна: last:border-0 — сброс разделителя у последнего пункта списка; border-transparent — ширина остаётся, гасится только цвет
stroke/thin1pxкласс border / inlineБазовая обводка карточек, полей, разделителей — доминирующее значение; вперемешку Tailwind-класс и inline 1px solid var(...). Переменная в inline-записи — это цвет (--border-card), не толщина
stroke/bold2pxinline, не border-2Активный таймер кормления/сна (QuickActionButton), выбранный тариф Premium — оба места инлайн-стилем, класс border-2 в коде есть, но не здесь

Тень, градиент, ритм и сетка

Как у opacity и обводки, семантического тира здесь нет: значения одноразовые, ссылаться вниз не на что — поэтому колонки «Semantic» не бывает, а сами токены по строению примитивы. «Реализация» показывается там, где механизм в коде не очевиден из значения.

shadow (elevation) 4 значения

Тень — только у элементов, плавающих поверх контента (навигация, попапы). У обычных карточек сигнал глубины — обводка stroke/thin (значения — в разделе Figma ↔ Код), не тень.

PrimitiveЗначениеГде используется
shadow/nav0 4px 24px rgba(0,0,0,.25)Плавающая нижняя навигация (BottomNav.tsx) и её skeleton-заглушка на старте (App.tsx) — дословный дубль значения в обоих местах, не общая константа
shadow/popover0 8px 24px rgba(0,0,0,.18)Поверхность выпадающих попапов — переключатель ребёнка, выбор метрики графика. Больше не дубль: вынесена в ui-kit/PopoverPanel.tsx
shadow-lgTailwind defaultБегунок Switch (ui/switch.tsx) — единственное место, где тень взята стоковым Tailwind-классом, а не собственным значением
focus-ring0 0 0 2px transparent, 0 0 0 4px var(--ds-accent-border)Кольцо фокуса Button (см. Components) — тоже box-shadow (ring-2 + ring-offset-2), не обводка; единственная тень с зазором (offset) в приложении

Библиотечный shadow-md в базовых классах ui/popover.tsx — фактически мёртвый: оба реальных потребителя (shadow/popover выше) переопределяют его инлайн-стилем. Тень DesktopFrame (0 24px 80px rgba(0,0,0,.5)) — вне основной шкалы: рендерится только вне мобильного Telegram (браузер, Telegram Desktop), не часть мобильного интерфейса.

gradient 2 значения

Только 2 реальных градиента, оба акцентные — завязаны на var(--ds-accent) текущего ребёнка, не статичная палитра.

PrimitiveЗначениеГде используется
ACCENT_PLATE_BG135deg, rgba(255,255,255,.18)→0 60%, поверх var(--ds-accent)Акцентная плашка-шапка (rounded-plate, см. corner radius) — Главная/История/Статистика, переключатель ребёнка. Единственный градиент, вынесенный в общий JS-экспорт: живёт в ui-kit/AccentPlate.tsx, фон задаёт сам PageHeader/AccentPlate
--accent-gradientaccent-gradient/* — color/{hue}/500 · 135°, stop1 opacity/30 → stop2 opacity/15Промо-карточка Premium (Settings.tsx) — CSS-переменная, 7 версий (по одной на каждый акцентный цвет ребёнка, см. Colors)

section header & rhythm 3 значения

Вертикальный ритм между секциями страницы — 20px практически везде. За это отвечает не отдельный «межсекционный» класс, а штатное схлопывание CSS-margin (margin collapse) между нижним отступом предыдущего блока и встроенным mt-5 компонента SettingsSectionHeader: браузер берёт максимум из двух соседних margin, не их сумму, поэтому 8/12px нижнего отступа карточки плюс 20px верхнего отступа заголовка дают ровно 20px, а не 28/32px. Эмуляция экрана с этим ритмом — в разделе layout & grid выше.

PrimitiveЗначениеРеализацияГде используется
heading/page20px / 700text-xl font-boldЗаголовок страницы — 16px до контента
heading/section11px / 600text-[11px] font-semiboldUppercase-лейбл секции — единообразно по всему приложению
rhythm/section-gap20pxmt-5, схлопывается с нижним отступом предыдущего блокаИнтервал перед новой секцией

Margin collapse работает только между block-уровневыми соседями — исключение здесь: компонент с базовым классом inline-flex, а inline-flex элементы в collapse не участвуют, поэтому интервал перед такой секцией держится на встроенном mt-5 заголовка, а не на схлопывании отступов.

layout 2 значения

Ширина мобильного контейнера и горизонтальные поля страницы. Разбор внутренних отступов компонентов, с картинками, остался в разделе Layout & grid.

PrimitiveЗначениеРеализацияГде используется
layout/container448pxmax-w-mdМаксимальная ширина мобильного контейнера, центрируется на широких экранах — то же значение, что и w-md в шкале width выше
layout/margin16pxpx-4Горизонтальные поля страницы на каждой вкладке

Другие кейсы

Готов к сотрудничеству

Ищу продуктовую команду. Если задача откликается — напишите.

Telegram