Buttons
Один primary на экран. Все остальные кнопки — тише по иерархии. Шесть вариантов, три размера, четыре состояния. Никаких custom-кнопок.
Анатомия
Padding 10/20 (md). Radius 12 (lg). Heading-font 600/15px. Gap 8 между текстом и иконкой. Иконка — Lucide, 16 px, stroke-2, наследует currentColor.
Варианты
Шесть вариантов покрывают все сценарии. Если кажется, что нужен седьмой — это значит, что иерархия неверна.
Размеры
Три размера. md — дефолт. sm — в плотных таблицах ЛК. lg — в hero и CTA-секциях.
Состояния
Hover — translateY(-2px) + soft shadow + цвет/90. Active — translateY(1px).
Focus — ring 3 px primary/50. Disabled — opacity 0.5, pointer-events none.
Иерархия на экране
Один primary на один view. Если действий два — primary + secondary. Три — primary + secondary + ghost. Четыре — это уже toolbar, нужны другие компоненты.
Один primary, один secondary — ясная иерархия.
Два primary рядом — взгляду не за что зацепиться.
Правила
| Правило | Объяснение | НЕ делать |
|---|---|---|
| Один primary на экран | Главное действие — одно. Остальные — secondary, ghost, link. | Два primary в hero, ещё один в toolbar. |
| Heading-font, 600 | Кнопка — Commissioner 600. Не Inter, не 700, не 500. | font-family: Inter в кнопке — смазывает голос. |
| Radius = lg (12) | Все кнопки md/lg — 12 px. Sm — 10 px. Без исключений. | Pill-кнопки (radius full) — это для chip, не для кнопок. |
| Глагол в label | «Сохранить», «Написать», «Начать проект». Действие, не состояние. | «OK», «Готово», «Подтверждение» — безличное. |
| Иконка справа от label | Стрелка/иконка — после текста, gap 8. Lucide, stroke-2, 16 px. | Иконка слева для CTA — ломает gaze-path. |
| Hover = lift, не цвет | Главный hover-сигнал — translateY(-2px) + shadow. Цвет — вторичный. |
Меняем оттенок на 30% и всё — чувствуется как «мёртвая». |
| Disabled = честно | Если кнопка disabled — рядом причина (tooltip, helper text). | Молчаливый disabled без объяснения, почему. |
Принципы
- Кнопка — обещание. Если на ней «Сохранить» — она ОБЯЗАНА сохранить. Без модала «вы уверены?» по умолчанию.
- Иерархия важнее декора. Primary всегда читается как «главное». Если хочется выделить secondary — значит, она должна быть primary.
- Нет custom-кнопок. Если нужный вариант не покрыт — добавляем его в систему, не на конкретной странице.
- Нет loading-spinner внутри label. При загрузке — меняем label на «Сохраняем…», disabled = true. Spinner — рядом, не вместо.
Tokens reference
| Property | Value | Source token |
|---|---|---|
| background (primary) | #0048B2 | --primary |
| color (primary) | #FFFFFF | --primary-foreground |
| radius | 12 px | --radius-lg |
| font-family | Commissioner | --font-heading |
| font-weight | 600 | — |
| transition | 200 ms · ease-standard | --duration-base |
| hover transform | translateY(-2px) | — |
| hover shadow | var(--shadow-hover) | --shadow-hover |
| focus ring | 3 px primary/50 | --ring |