Mom's Helper — дизайн-система

Роль: Основатель и продуктовый дизайнер Период: 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Подтверждение, идёт/активно — фон плашки
--warningwarningПредупреждение, error-баннер Dashboard
--warning-bgwarning-bgПредупреждение, error-баннер Dashboard — фон плашки
--dangerdangerУдаление, ошибки ввода
--danger-bgdanger-bgУдаление, ошибки ввода — фон плашки
--infoinfoКормление в EventRow/History
--info-bginfo-bgКормление в EventRow/History — фон плашки

Light

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

Semantic tokens — Accent ребёнка

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

Вариант--accent--accent-light--accent-text--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 с одним форм-фактором, а не адаптивный сайт под разные вьюпорты. Правила ниже — из кода, а не из макета.

Модуль

Базовая единица пространства — 4px, в коде это --space-unit: 0.25rem. Из неё выведены все отступы, зазоры, радиусы и высоты контролов. Есть и микромодуль 2px — половина шага, только для отбивки строк текста внутри блока, где полный шаг уже слишком много.

В Figma шкала выгружена целиком, 33 значения вплоть до 448px — но это справочник, а не рабочий набор. Реально в коде живут десять ступеней:

2px микромодуль
4px 1 × 4
6px 1.5 × 4
8px 2 × 4
10px 2.5 × 4
12px 3 × 4
14px 3.5 × 4
16px 4 × 4
20px 5 × 4
24px 6 × 4
ТокенВ кодеГде применяется
space/2p-0.5 · gap-0.5Отбивка подписи от заголовка, зазор внутри бейджа
space/4p-1 · gap-1Зазор между иконкой и подписью в плитке
space/6p-1.5 · gap-1.5Вертикальный паддинг мелкой кнопки
space/8p-2 · gap-2Основной зазор: сетки карточек, хвост строки
space/10p-2.5 · gap-2.5Зазор между крупными плитками Быстрой записи
space/12p-3 · gap-3Вертикальный паддинг строки, зазор иконка ↔ текст
space/14p-3.5Вертикальный паддинг крупной кнопки
space/16p-4 · gap-4Поля страницы, паддинг карточки — самое частое значение
space/20p-5 · mb-5Отступ перед заголовком секции
space/24p-6Боковой паддинг крупной кнопки, поля экрана-заглушки

Три вида отступа

Одна и та же ступень значит разное в зависимости от того, что она разделяет.

ВидВ кодеПравило
Внутренний — от края блока до содержимогоp- · px- · py-Принадлежит компоненту: карточка и строка задают его сами, снаружи не переопределяется
Между соседями внутри блокаgap-Тоже принадлежит компоненту — это его внутреннее устройство, а не вёрстка страницы
Внешний — до следующего блокаmb-Принадлежит странице, а не компоненту: сколько воздуха между блоками — решает экран. Поэтому у карточки списка своего внешнего отступа нет

Отступы

Всё кратно четырём: базовая единица --space-unit: 0.25rem, то есть 4px. Ниже — как это разложено по уровням, от контейнера страницы до зазора между иконкой и текстом.

УровеньЧем заданЗначение и где
Контейнер страницыmax-w-md · mx-auto448px, по центру. Один на все страницы — это Mini App с одним форм-фактором
Поля страницыpage-content · px-416px по бокам. Так открываются все семь экранов
Низ страницыsafe-area-inset-bottom + 96pxЗапас под плавающую навигацию, чтобы последний блок не уезжал под неё
Плавающая навигацияsafe-area-inset-bottom + 10pxОтсчёт от системной области, а не от края окна
Паддинг карточкиp-416px со всех сторон
Паддинг строки спискаpx-4 · py-316 × 12px, минимум 52px высоты. Задаёт сам компонент, см. ListRow
Зазор в сеткеgap-28px: сетки карточек, плиток, хвост строки
Зазор иконка ↔ текстgap-312px
Между блокамиmb-2 · mb-3 · mb-4 · mb-58 / 12 / 16 / 20px — шаг выбирается по весу блока

Компоненты

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

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

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

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

Примитивы

Собраны сами по себе — из токенов и разметки, без других компонентов внутри. Критерий тот же, что в сводной таблице: тип определяется тем, из чего компонент собран, а не тем, что он импортирует. Поэтому PopoverPanel — примитив, хотя стоит поверх Radix: своя у него только поверхность.

Button ui-kit/Button.tsx

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

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

Состав

ЭлементТокенЗначение
ПодписьBodyPrimary · ButtonCaption14/500 — md и lg; 12/500 — sm
Скруглениеrounded-control10px
Обводкаstroke/thin · border/card1px у secondary и subtle; прозрачная (не отсутствует — иначе active сдвигал бы кнопку на 1px) у primary и danger; нет у ghost
Высота — минимум, не фиксmin-h-8 · min-h-11 · min-h-1332 / 44 / 52px
Отступы по бокамspace/12 · space/20 · space/2412 / 20 / 24px
Зазор иконка ↔ подписьgap-1-56px
Заливка--accent · --bg-card · --danger · --icon-btn-bgакцент / карточка / danger / прозрачная / icon-btn
Цвет подписи--text-primary · --text-secondaryбелый на заливке, иначе основной или вторичный

Варианты

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

Состояния

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

Свойства

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

Switch ui/switch.tsx

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

44 × 24px

Состав

ЭлементТокенЗначение
Трекw-11 · h-644 × 24px
Бегунокh-5 · space/2020 × 20px, белый
Сдвиг включённогоtranslate-x-520px
Отступыborder-2нет — инсет под бегунок создаёт прозрачная рамка: 24 − 2×2 = 20px
Скруглениеrounded-fullполное — и трек, и бегунок
Заливка вкл / выкл--accent · --bg-inputакцент / фон поля
Тень бегункаshadow-lg · opacity/25мягкая, 25% чёрного
focus-visible--accent-borderкольцо 2px внутрь
disabledopacity/50гаснет целиком
pressed—нет — его не было ни у одного из двух прежних компонентов, при объединении не добавляли

Состояния

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

Свойства

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

Badge ui-kit/Badge.tsx

Пилюля со статусом или тегом: «скоро», «Premium», счётчик. Не нажимается, и об этом говорит форма — всегда rounded-full, тогда как всё нажимаемое рядом прямоугольное. Поэтому переопределять форму по месту нельзя.

neutralaccentsuccesswarningdangerinfoxs / 8px
neutralaccentsuccesswarningdangerinfosm / 10px
neutralaccentsuccesswarningdangerinfomd / 12px

Состав

ЭлементТокенЗначение
Подпись — mdButtonCaption12/500
Подпись — smSmallBadge10/500
Подпись — xsMicroBadge8/500
Отступыspace/10 · space/8 · space/610 × 4px у md · 8 × 2px у sm · 6 × 2px у xs
Скруглениеrounded-fullполное, всегда
Зазор иконка ↔ подписьgap-14px
Тона (6)--accent-light · --success-bg · --warning-bg · --danger-bg · --info-bg · --bg-inputполупрозрачная заливка и насыщенный текст того же цвета

Свойства

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

Avatar ui-kit/Avatar.tsx

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

👶
photo
👶
sm / 32px
👶
photo
👶
md / 40px
👶
photo
👶
lg / 56px

Состав

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

Свойства

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

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

Input — готовое поле ввода, текст или число. Field под ним — не поле, а основа: подпись над контролом и общая строка классов поверхности FIELD_SURFACE. Сам по себе Field ничего не рисует — без label он возвращает содержимое как есть. Тот же Field используют SelectField и TimeSelect, поэтому подпись и высота у всех трёх одинаковые.

Состав

ЭлементТокенЗначение
Поле — высотаh-1144px
Поле — отступ по бокамspace/1616px
Поле — текстBodyPrimary14/400
Поле — обводка и заливкаborder/card · --bg-input1px, фон поля
Подпись над полемButtonCaption · space/612/500, отступ снизу 6px
Скруглениеrounded-control10px

Состояния

default focus-visible disabled Введите имя ребёнка error
СостояниеТокенЧто меняется
default—Базовый вид
focus-visible--accent-borderКольцо 2px без зазора — у поля уже есть своя обводка.
disabledopacity/60Гаснет, курсор запрета
error--dangerОбводка поля становится danger. Сообщение поле не показывает — его рисует Notice под формой; обводка нужна, чтобы было видно, какое именно поле чинить

Свойства

СвойствоНазначение
labelПодпись над полем — только у Input
valueЗначение
onChangeОбработчик ввода
placeholderПодсказка в пустом поле — только у Input
disabledЗаблокировано
errorНе прошло проверку — обводка danger. Есть и у SelectField

Что содержит

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

Skeleton ui-kit/Skeleton.tsx

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

Состав

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

Варианты

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

Свойства

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

AccentPlate ui-kit/AccentPlate.tsx

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

Состав

ЭлементТокенЗначение
Скруглениеrounded-plate28px
Отступыspace/1616px со всех сторон
Отступ снизуspace/2020px
Заливка--accent-gradientградиент поверх акцентного цвета

Свойства

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

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

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

👶 Маша
👧 Соня
🍼 Кормлений
🥕 Прикорм скоро

Состав

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

Свойства

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

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

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

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

Состав

ЭлементТокенЗначение
Дорожка ползункаh-1-5 · rounded-full6px, полное скругление
Кружок ползунка--accent18×18px — вне шкалы space, инлайн CSS (::-webkit-slider-thumb/::-moz-range-thumb в index.css, не Tailwind-класс)
Фокус-кольцо кружка--accent-border2px, с зазором 2px от кружка
Подпись--text-secondary12/400, text-secondary
Значение--text-primary14/700, text-primary
Отступ строки подписи до ползункаmb-1-56px
Ползунок—как отдельный Slider

Состояния

Показаны на голом Slider: у SliderField своих состояний нет, он их только оборачивает.

default
disabled
СостояниеТокенЧто меняется
default--accentЗаполненная часть дорожки и бегунок — акцентом, через accent-color. Вид бегунка отдан браузеру
disabledopacity/60Гаснет целиком, курсор запрета
focus—Видимого состояния нет: outline-none снимает системное кольцо, и своего взамен не поставлено — единственный интерактивный элемент набора без фокус-кольца

Свойства

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

Составные

Собраны из примитивов. Здесь же лежат компоненты, знающие про домен приложения — ребёнка, Premium, типы записей, — тогда как примитивы про данные не знают ничего.

Notice «плашка-сообщение» ui-kit/Notice.tsx

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

Не удалось загрузить данные
Новый часовой пояс: UTC+3
Попробуйте бесплатно 7 дней
Без привязки карты
Введите имя ребёнка

Состав

ЭлементТокенЗначение
ТекстBodyPrimary14/400
Заголовокweight/semiboldтолько когда задан title
Отступыspace/16 · space/1216 × 12px
Скруглениеrounded-control10px
Обводка--danger · --success · --accent1px цветом тона
Заливка--danger-bg · --success-bg · --accent-lightполупрозрачная, по тону
КрестикButtonvariant ghost, size sm — 32×32, иконка 16px
Зазор до действийspace/1212px

Варианты

ТонТокенКогда
danger--dangerОперация не удалась — ошибка формы, сбой загрузки, неудачное удаление
success--successУспех или предложение — пробный период
accent--accentНейтральное уведомление — смена часового пояса

Свойства

СвойствоНазначение
tonedanger (по умолчанию) · success · accent
titleЗаголовок над текстом. Без него сообщение — одна строка
childrenТекст сообщения
actionsКнопки справа. Ожидается Button с size sm, а не голая разметка
onDismissКрестик закрытия. Проп, а не содержимое слота: он часть анатомии — всегда справа последним, всегда с aria-label. Рисуется Button в варианте ghost: закрыть — самое второстепенное действие в плашке

ScreenState «экран-заглушка» ui-kit/ScreenState.tsx

Полноэкранное состояние вместо всего приложения: обрыв связи с сервером и подгрузка страниц вне мини-аппа. Внутри мини-аппа загрузку показывают скелетоны — по фидбэку пользователя спиннер «проскакивал» и прыгал на настоящие данные. Отличается от Notice ролью — тот живёт внутри страницы рядом с содержимым, этот заменяет собой страницу целиком.

👶
Загрузка...
загрузка
⚠️
Ошибка подключения
Не удалось подключиться к серверу
ошибка

Показано в уменьшенной рамке: в приложении блок занимает весь экран.

Состав

ЭлементТокенЗначение
Эмодзи—48px — иллюстрация, а не значок; в шкалу иконок не входит
ЗаголовокBodyPrimary · weight/semiboldтолько когда задан title
ТекстBody · --text-secondary14/400 вторичным
Фон--bg-pageфон страницы, не карточки
Высотаmin-h-screenвесь экран, содержимое по центру
Отступ по бокамspace/2424px

Состояния

Оба состояния показаны в превью выше: у компонента их ровно два, и они и есть весь компонент — отдельного «базового» вида, от которого они отличались бы, не существует.

СостояниеТокенЧто меняется
загрузка—Эмодзи 👶, текст «Загрузка...», спиннер вместо кнопки. Suspense на витрине разработчика, юридических страницах и лендинге. В самом мини-аппе загрузку показывают скелетоны, а не этот экран
ошибка--accentЭмодзи ⚠️, заголовок, текст ошибки от сервера и кнопка перезагрузки страницы

Свойства

СвойствоНазначение
emojiКрупная иллюстрация сверху
titleЗаголовок. Без него состояние — эмодзи и строка текста
childrenПояснение под заголовком
actionКнопка или спиннер под текстом

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

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

три опции, с иконками
две опции, без иконок
compact — в жёлобе, на всю ширину

Состав

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

Состояния

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

Свойства

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

QuickActionButton ui-kit/QuickActionButton.tsx

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

Состав

ЭлементТокенЗначение
ПодписьBodyPrimary · ButtonCaption14/500 у primary · 12/500 у secondary
Эмодзиfont-size/24 · font-size/1624px у primary, 16px у secondary
Высотаspace/96 · space/8096px primary · 80px secondary
Обводкаstroke/bold · stroke/thin2px акцентная · 1px нейтральная
Скруглениеrounded-control10px
Зазор эмодзи ↔ подписьgap-2 · gap-1-58px primary · 6px secondary
Заливка--bg-card · --success-bgфон карточки; success при active
Паддинг — primaryинлайн16 × 8px — не классами, вне шкалы Tailwind
Паддинг — secondaryинлайн10 × 4px — не классами, вне шкалы Tailwind

Состояния

Показаны на компактной плитке — у крупной состояния те же, меняются только размеры и толщина рамки. В приложении, впрочем, активной бывает только крупная, а выключенной только компактная: см. таблицу.

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

Свойства

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

ListRow ui-kit/ListRow.tsx

Кликабельная строка со слотами — основной способ показать список в приложении: так собраны настройки, все шторки, модалки, Дашборд и Premium.

В интерфейсе

bordered — на фоне страницы
ПРОФИЛЬ
👶
Маша
4 месяца
🎨
Оформление
по умолчанию — внутри страницы
ВНЕШНИЙ ВИДPremium
🔔
Кормления
каждые 3 часа
😴
Сон

Правый хвост

Слот trailing — то, чем строки на самом деле отличаются друг от друга; ради него компонент и появился. Ниже каждый элемент показан один раз; сочетания из них перечислены в таблице, чтобы не повторять одно и то же по три раза.

Значок
шеврон, галочка, правка, внешняя ссылка, плюс
Время
когда была запись
21:14
Бейдж
пункт «скоро»
скоро
Переключатель
включить пункт
Кнопка
разовое действие
Пилюля-действие
выбор в модалке
Оставить
Скелетон
заглушка тумблера при загрузке
Ручка перетаскивания
отдельный проп, всегда последняя
ЭлементРазмерГде
Значокicon/smШеврон и галочка — хвост по умолчанию у кликабельной и выбранной строки. Правка в Истории и «Настройках», внешняя ссылка в Premium
Значок «плюс»icon/mdДобавить событие в шторке выбора. Единственный хвост крупнее остальных — расхождение, а не решение
Время11px, вторичнымИстория, последние кормления и сны
Бейдж «скоро»badge/smНереализованный пункт
Переключатель44×24Семь мест: фильтр истории, настройки уведомлений, шторки настройки карточек и графиков
Кнопкаbutton/sm«Удалить» у соопекуна. В коде собрана вручную, а не из Button — расхождение
Пилюля-действиеbadge/sm«Оставить» в модалке выбора ребёнка. Тоже вручную: сплошной акцент с белой подписью, тона под это в Badge нет
Скелетон36×20На месте переключателя, пока грузятся настройки
Ручка перетаскивания44×44 · icon/mdНе часть слота, а свой проп onDragStart: всегда последняя, тянуть можно только за неё
Сочетания—Встречаются четыре: время + значок (История), время + бейдж (последние кормления и сны), бейдж + переключатель (нереализованный пункт с тумблером), переключатель + ручка (перетаскиваемая строка)

Состав

ЭлементТокенЗначение
Метрика строкиspace/16 · space/12 · min-h/5216px по бокам, 12px сверху и снизу, минимум 52px высоты — задаёт сам компонент
Иконка категорииtext-xl w-6эмодзи 20px в колонке 24px — 13 вызовов из 20
Аватар (leading)rounded-full · фон --accent-light36px, круг — 2 вызова, оба собраны вручную мимо Avatar
ЗаголовокBodyPrimary14px / 500
Заголовок, деструктивный--danger14px / 500
Подпись и заметкаBodySecondary · space/212px, отступ сверху 2px
Зазор иконка ↔ текстgap-312px; 8px только в перетаскиваемой строке — там место занимает ручка
Зазор внутри хвостаgap-28px
Скруглениеrounded-control 10px + overflow-hiddenнет — несёт контейнер группы
Разделитель--row-divider1px, рисует карточка через divide-y — сама строка о нём не знает
Высота строкиmin-h/52минимум 52px, дальше растёт по содержимому — около 62px с подписью

Состояния

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

Свойства

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

Карточка и заголовок секции

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

Варианты

ВариантТокенКогда
bordered--bg-card · --border-cardГруппа лежит прямо на фоне страницы — по общему правилу «блок на фоне страницы с обводкой»
по умолчанию--bg-card2Группа внутри страницы ребёнка, где обводки не было ни у одной секции

Состав карточки

ЭлементТокенЗначение
Заголовок секцииSectionLabel11/600, межбуквенный 5% — не настраивается
Отступы заголовкаspace/20 · space/820px сверху, 8px снизу
Скругление карточкиrounded-control10px, с обрезкой содержимого
Заливка карточки--bg-card2 · --bg-cardcard2 внутри страницы, card с обводкой на фоне страницы
Разделитель строк--row-divider1px между строками, рисует группа, а не строка
Внешний отступ—Своего нет: расстояние до следующего блока задаёт страница. Раньше в компоненте стоял 8px, и 17 вызовов из 23 его перебивали — 12 в ноль

Экспорты

СвойствоНазначение
ListHeaderЗаголовок секции. Слот action — элемент справа. В приложении применён один раз: бейдж «Premium» в разделе «Внешний вид»
ListКарточка со строками. Проп bordered переключает заливку и обводку
ListRowСтрока. Отдельного «SettingsRow» больше нет — прослойка с шестью применениями против одиннадцати прямых вызовов основы себя не окупала
хвост по умолчаниюГалочка у выбранной строки, шеврон у кликабельной, ничего у некликабельной — иконки 16px, младший размер шкалы. Задаётся trailing, если нужен свой

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

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

Состав

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

Состояния

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

Что содержит

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

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

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

🌙Ночной сон — 9 ч 40 мин
Кормление
5 раз
750 мл всего
2 ч 10 мин назад · 150 мл
Сон
5 ч 20 мин
1 ч 40 мин назад
Подгузник
🧷
Нет записей
Вес
7.2 кг
↓ -0.2vs прошлая
скоро
Прикорм
🥕

Состав

ЭлементТокенЗначение
ПодписьBodySecondary · space/812px, отступ снизу 8px
ЗначениеNumber24/700, интерлиньяж 1
Единицаtext-base font-normal · --text-secondary (один сайт, не заведён как стиль)16/400
Иконка вместо значенияtext-2xl opacity-4024px, 40%
Вторичная строкаBodySecondary · space/812px, отступ сверху 8px
Футерmt-2 pt-2 · разделитель --row-divider12px, отступ и поля 8px
Бейджabsolute top-2 right-28px от верха и правого края
Отступыp-416px по кругу
Скругление и фонrounded-control · --bg-card10px, без обводки

Состояния

Кормление
5 раз
750 мл всего
default
Вес
⚖️
Нет записей
нет данных
скоро
Прикорм
🥕
dimmed «скоро»
СостояниеТокенЧто меняется
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 он рендерится по-разному на разных платформах.

:

Состав

ЭлементТокенЗначение
Высотаh-1144px — как у Input
ТекстBodyPrimary14/400
Отступ по бокамspace/1212px
Обводкаstroke/thin · border/card1px
Скруглениеrounded-control10px
Заливка триггера--bg-input · --bg-cardфон поля · фон карточки при tone="card"
Выпадающий список--bg-card · shadow/popoverфон карточки, обводка 1px, тень
Пункт списка--accent-light14/400, подсветка выбранного акцентом

Состояния

default open focus disabled active
СостояниеТокенЧто меняется
default—Базовый вид
open—Список раскрыт, шеврон повёрнут
focus-visible--accent-borderКольцо 2px без зазора — как у Input
disabledopacity/50Гаснет, курсор запрета
active--accentОбводка 2px акцентом. Не проп, а className по месту: так помечен селектор сравнения в Статистике — единственный контрол, который включён всегда

Свойства

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

Sheet ui-kit/Sheet.tsx

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

Кормление · 09:30
Сегодня 🔒 Просмотр
🍼
150 мл
Смесь
Записать кормление
Тип кормления
🤱Левая
🤱Правая
🍼Смесь
Время

Состав

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

Состояния

Превью здесь нет намеренно: оба состояния шторки — движение, а не вид. «Открыта» и «закрыта» отличаются положением по вертикали и затемнением фона, свайп — это жест. Статичная картинка показала бы только конечное положение, которое и так видно в превью выше.

СостояниеТокенЧто меняется
open / closedopacity/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, карандаш в рамке) в Истории и Настройках. Ни то, ни другое не решение — просто так сложилось.

Размеры

Три размера — 16, 20, 24. Все кратны --space-unit (4px), той же базе, на которой стоят отступы и скругления: иконки не исключение из общей шкалы. Раньше значений было девять и размер задавал каждый вызывающий код — те, что создавали разнобой (11, 14, 18, 22, 26), ровно те, что не кратны четырём.

ОбразецЗначениеРеализацияГде используется
16pxsize={16}Инлайн рядом с текстом — галка выбора, «Изменить», внешняя ссылка. Шеврон списка, мелкое действие, эмодзи в ChipSelect, иконка в кнопке sm. 18 мест
20pxsize={20}Значок в строке списка, действие в шапке и шторке, все четыре таба BottomNav, переключатель темы, иконка в кнопке md. 5 мест
24pxICON_PX · fontSizeЭмодзи плитки QuickActionButton в варианте primary и иконка в кнопке lg. Размер выводится из размера компонента, а не задаётся вызовом

Единственное исключение — эмодзи 48px на заставке поворота экрана: это иллюстрация во весь экран, а не значок, и в шкалу иконок её не загоняли.

Штрих и цвет

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

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

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

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-lg…text-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%Подтверждение, идёт/активно (фон)
--warningwarningcolor/amber/500Предупреждение, error-баннер Dashboard
--warning-bgwarning-bgcolor/amber/500 15%Предупреждение, error-баннер Dashboard (фон)
--dangerdangercolor/red/500Удаление, ошибки ввода
--danger-bgdanger-bgcolor/red/500 15%Удаление, ошибки ввода (фон)
--infoinfocolor/blue/500Кормление в EventRow/history
--info-bginfo-bgcolor/blue/500 15%Кормление в EventRow/history (фон)

Light

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

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

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

CSS-переменнаяSemanticPrimitiveГде используется
--accentaccent/defaultcolor/neutral-accent/500Цвет CTA/аватара, пока ребёнку не выбрали свой
--accent-lightaccent-light/defaultcolor/neutral-accent/500 15%Подложка под accent (default)
--accent-textaccent-text/defaultcolor/neutral-accent/300Текст поверх accent-light (default)
--accent-borderaccent-border/defaultcolor/neutral-accent/500 40%Обводка инфо-блока/плашки (default)
--accentaccent/purplecolor/purple/500Цвет CTA/аватара — вариант «Фиолетовый»
--accent-lightaccent-light/purplecolor/purple/500 15%Подложка под accent (Фиолетовый)
--accent-textaccent-text/purplecolor/purple/300Текст поверх accent-light (Фиолетовый)
--accent-borderaccent-border/purplecolor/purple/500 40%Обводка инфо-блока (Фиолетовый)
--accentaccent/bluecolor/blue/500Цвет CTA/аватара — вариант «Синий»
--accent-lightaccent-light/bluecolor/blue/500 15%Подложка под accent (Синий)
--accent-textaccent-text/bluecolor/blue/300Текст поверх accent-light (Синий)
--accent-borderaccent-border/bluecolor/blue/500 40%Обводка инфо-блока (Синий)
--accentaccent/greencolor/green/600Цвет CTA/аватара — вариант «Зелёный»
--accent-lightaccent-light/greencolor/green/600 15%Подложка под accent (Зелёный)
--accent-textaccent-text/greencolor/green/400Текст поверх accent-light (Зелёный)
--accent-borderaccent-border/greencolor/green/600 40%Обводка инфо-блока (Зелёный)
--accentaccent/pinkcolor/rose/500Цвет CTA/аватара — вариант «Розовый»
--accent-lightaccent-light/pinkcolor/rose/500 15%Подложка под accent (Розовый)
--accent-textaccent-text/pinkcolor/rose/300Текст поверх accent-light (Розовый)
--accent-borderaccent-border/pinkcolor/rose/500 40%Обводка инфо-блока (Розовый)
--accentaccent/orangecolor/amber/600Цвет CTA/аватара — вариант «Оранжевый»
--accent-lightaccent-light/orangecolor/amber/600 15%Подложка под accent (Оранжевый)
--accent-textaccent-text/orangecolor/amber/300Текст поверх accent-light (Оранжевый)
--accent-borderaccent-border/orangecolor/amber/600 40%Обводка инфо-блока (Оранжевый)
--accentaccent/tealcolor/teal/500Цвет CTA/аватара — вариант «Бирюзовый»
--accent-lightaccent-light/tealcolor/teal/500 15%Подложка под accent (Бирюзовый)
--accent-textaccent-text/tealcolor/teal/300Текст поверх accent-light (Бирюзовый)
--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-*) и у прозрачностей, запечённых в цветовые переменные (--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-* в проекте нет, процент запечён внутрь цветовой переменной (--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; в проде объединено в --danger-bg (0.12/0.15)
--success-bgopacity/1212запечено в переменной, светлая темаФон статус-плашек, светлая тема — по одной переменной на статус: --success-bg, --warning-bg, --danger-bg, --info-bg
--success-bg · --accent-lightopacity/1515запечено в переменныхФон статус-плашек тёмная тема (те же 4 переменные); фон --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, другой механизм
--accent-borderopacity/4040смешанно: класс opacity-40, inline-style и запечено в переменнойИконки нереализованных функций и disabled-пагинация Истории (класс), disabled-опции в шторках (inline), кольцо фокуса и обводки на акценте (--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(--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(--accent) текущего ребёнка, не статичная палитра.

PrimitiveЗначениеГде используется
ACCENT_PLATE_BG135deg, rgba(255,255,255,.18)→0 60%, поверх var(--accent)Акцентная плашка-шапка (rounded-plate, см. corner radius) — Главная/История/Статистика, переключатель ребёнка. Единственный градиент, вынесенный в общий JS-экспорт: живёт в ui-kit/AccentPlate.tsx, фон задаёт сам 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 компонента ListHeader: браузер берёт максимум из двух соседних 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Горизонтальные поля страницы на каждой вкладке