Кнопки и ссылки
Девять вариантов кнопки, три размера, группа, квадратная под иконку — и ссылка, которую ресет иначе оставил бы неотличимой от текста.
Класс ставится на любой элемент — <button>,
<a>, <input type="submit">. Оформление
одинаковое, семантику выбирает разметка: то, что отправляет форму, —
кнопка; то, что ведёт на другой адрес, — ссылка.
Варианты
| Класс | Вид | Когда |
|---|---|---|
.gr-btn | Поверхность с границей | Обычное действие, каких на экране много |
.gr-btn-primary | Заливка акцентом | Главное действие экрана — одно на форму |
.gr-btn-secondary | Контур акцентом | Действие рядом с главным: «Отмена», «Назад» |
.gr-btn-success | Заливка зелёным | Подтверждение, публикация |
.gr-btn-warning | Заливка янтарным | Действие с последствиями, но обратимое |
.gr-btn-danger | Заливка красным | Необратимое: удаление, отзыв доступа |
.gr-btn-info | Заливка пурпурным | Справка, подробности |
.gr-btn-ghost | Без фона и границы | Панель инструментов, плотный ряд действий |
.gr-btn-link | Выглядит ссылкой | Третьестепенное действие внутри текста |
<button class="gr-btn gr-btn-primary">Сохранить</button>
<button class="gr-btn gr-btn-secondary">Отмена</button>
Цвет текста на заливке — один токен на три статуса.
Заливка берёт --gr-color-success и подобные, а текст —
--gr-color-on-accent. В светлой теме статусные цвета тёмные,
а on-accent белый; в тёмной они светлеют, а on-accent
становится почти чёрным. Пара остаётся читаемой в обеих темах,
и второго набора правил под тёмную не требуется.
Предупреждение из этого правила выпадает. Чтобы
--gr-color-warning читался текстом на белом, он обязан
быть тёмно-коричневым — а прямоугольник такого цвета читается не как
«внимание», а как «оливковый». Поэтому у статуса есть своя пара
--gr-color-warning-surface и --gr-color-on-warning:
в обеих темах кнопка остаётся янтарной с тёмным текстом. Зелёный, красный
и пурпурный узнаваемы и в тёмном варианте, своей пары им не нужно.
Наведение и нажатие
Вариант задаёт цвета четырьмя переменными — --gr-btn-bg,
--gr-btn-fg, --gr-btn-border и триплетом
--gr-btn-fg-hsl. Состояния :hover
и :active написаны один раз на все девять: они кладут поверх
фона варианта вуаль из его же цвета текста. Насколько это темнее или
светлее, решает действующая тема, а не таблица заготовленных оттенков.
--gr-btn-veil-hover: hsl(var(--gr-btn-fg-hsl), 12%);
--gr-btn-veil-active: hsl(var(--gr-btn-fg-hsl), 24%);
/* вуаль ложится внутренней тенью — поверх фона, но под текстом */
box-shadow: inset 0 0 0 100vmax var(--gr-btn-veil-hover);
Вуаль в 12 % даёт ровно то же, что дало бы смешение 88 % фона с 12 % текста,
и не требует color-mix(): альфу приписывают к записи
hsl(), поэтому цвет текста варианта продублирован триплетом.
Свой цвет вуали задают, переопределив --gr-btn-veil-hover
и --gr-btn-veil-active; значение transparent
снимает подсветку вовсе — так сделано у .gr-btn-link.
Подсветка живёт внутри @media (hover: hover). На сенсорном
экране :hover залипает после касания — кнопка остаётся
подсвеченной до тех пор, пока не тронут другую.
Размеры
| Класс | Высота | Токен |
|---|---|---|
.gr-btn-sm | 2rem | --gr-control-height-sm |
.gr-btn | 2.5rem | --gr-control-height |
.gr-btn-lg | 3rem | --gr-control-height-lg |
Высоты заданы в rem, а не в пикселях. Режим для слабовидящих
поднимает кегль корня документа — и кнопки растут вместе со страницей,
без единого дополнительного правила. Включите переключатель
«Для слабовидящих» на боковой панели и сравните.
Форма
| Класс | Поведение |
|---|---|
.gr-btn-block | Во всю ширину родителя; display меняется на flex, иначе кнопка оставила бы за собой строку |
.gr-btn-icon | Квадратная: ширина берётся из той же переменной, что и высота, поэтому .gr-btn-icon.gr-btn-lg остаётся квадратом |
.gr-btn-group | Слипшийся ряд: скругления остаются только на краях, соседние границы не удваиваются |
У квадратной кнопки внутри только знак — подписи нет, значит нет и имени
для скринридера. aria-label обязателен: без него кнопка
озвучивается как «кнопка» и ничего больше.
Состояния
| Состояние | Как задаётся | Что происходит |
|---|---|---|
| Недоступна | disabled или aria-disabled="true" | Приглушается, курсор not-allowed. Атрибута disabled у <a> не существует — там нужен aria-disabled |
| Занята | aria-busy="true" | Приглушается, курсор progress |
| Фокус с клавиатуры | :focus-visible | Контур толщиной --gr-focus-width цветом --gr-color-focus |
Кольцо загрузки у aria-busy появится вместе с модулем
_spinner — это волна 8c. Сейчас состояние видно
по приглушению и курсору, и этого достаточно, чтобы кнопку не нажали дважды.
Ссылки
Компонент нужен ровно потому, что ресет снимает с <a>
и цвет, и подчёркивание (color: inherit; text-decoration: none).
Это сделано намеренно — ссылка-обёртка вокруг карточки не должна красить
её содержимое, — но обычная ссылка в тексте без класса остаётся
неотличимой от текста.
| Класс | Вид | Когда |
|---|---|---|
.gr-link | Цвет ссылки, подчёркивание, три состояния | Ссылка в тексте — случай по умолчанию |
.gr-link-muted | Цвет вторичного текста | Подписи, футер, служебные переходы |
.gr-link-inherit | Цвет наследуется от окружения | Ссылка-заголовок, ссылка на цветной плашке |
.gr-link-quiet | Подчёркивание только при наведении и фокусе | Навигация, ряд карточек — но не текст абзаца |
.gr-link-external | Стрелка-указатель после текста | Переход на чужой сайт или в новую вкладку |
Стиль линии
Линия несёт смысл, которого нет у цвета, и переживает чёрно-белую печать.
Стиль сочетается с любым вариантом: .gr-link-quiet.gr-link-dotted
покажет точки при наведении, .gr-link-muted.gr-link-dashed —
пунктир приглушённым цветом.
| Класс | Линия | Чем принято помечать |
|---|---|---|
| — | Сплошная | Обычный переход |
.gr-link-dotted | Точками | Термин со справкой, сноска — то, что откроется рядом |
.gr-link-dashed | Пунктиром | То же, но заметнее |
.gr-link-wavy | Волной | Проблемное: устаревший адрес, неточный перевод |
.gr-link-double | Двойная | Главная ссылка абзаца |
Сплошная — обычный переход.
Точками — термин со справкой.
Пунктиром — пояснение рядом.
Волной — устаревший адрес.
Двойная — главная ссылка абзаца.
Сочетание — приглушённая пунктиром.
Линия объявлена только длинными свойствами —
text-decoration-line, -thickness,
-style. Сокращённое text-decoration сбрасывает
все остальные длинные в исходные значения, поэтому одно
text-decoration: underline в правиле :hover
обнуляло бы толщину from-font из базового класса — и линия
у тихой ссылки переставала совпадать с линией у обычной.
Толщина from-font берётся у шрифта, поэтому остаётся
соразмерной кеглю и в подписи, и в заголовке. Единственное место, где
она уступает, — режим для слабовидящих: там линию рисует правило темы,
и она нарочно толще.
Обычная ссылка внутри абзаца — цвет и подчёркивание.
Приглушённая: условия использования.
Наследующая цвет: заголовок-ссылка.
Тихая (наведите или пройдите табом): раздел навигации.
Внешняя: репозиторий (откроется в новой вкладке).
<a class="gr-link" href="/страница">обычная ссылка</a>
<a class="gr-link gr-link-external" href="https://пример" target="_blank" rel="noopener">
внешняя<span class="gr-sr-only"> (откроется в новой вкладке)</span>
</a>
Стрелка — признак только для глаза. Она нарисована
псевдоэлементом, а его нельзя пометить aria-hidden, и
скринридер прочитает её как «стрелка на северо-восток». О переходе
на чужой сайт или об открытии новой вкладки сообщает текст
в .gr-sr-only рядом — как в примере выше.
У .gr-link-quiet есть граница применимости.
Снимать подчёркивание можно там, где ссылка опознаётся по положению:
список навигации, ряд карточек, футер. В тексте абзаца положение
не говорит ни о чём, и ссылка остаётся отличима только цветом — то есть
не отличима вовсе для дальтоника и на чёрно-белой печати.
Режим для слабовидящих перекрывает оба «тихих» варианта: правило
[data-gr-a11y="low-vision"] a[href] из темы возвращает
и цвет ссылки, и подчёркивание. Оно выигрывает по специфичности,
потому что тема приезжает в каждую сборку и попадает в тот же слой,
что и компоненты. Включите переключатель на боковой панели
и посмотрите на тихую ссылку выше.
Ссылка и кнопка-ссылка — разные вещи
.gr-link | .gr-btn-link | |
|---|---|---|
| Элемент | <a href> | <button> |
| Что делает | Переход по адресу | Действие на текущей странице |
| Клавиша | Enter | Enter и Space |
| Высота | По строке текста | --gr-control-height: выравнивается в ряду кнопок |
Выбирает разметка, оформление лишь подтверждает выбор. Кнопка, ведущая на другой адрес, ломает Ctrl+клик, среднюю кнопку мыши и контекстное меню «Открыть в новой вкладке»; ссылка, выполняющая действие, отправляет в историю браузера запись, по которой некуда вернуться.
Отступы — утилитами
Своих внешних полей у кнопки нет: расстановка — дело раскладки.
Слой griffincss.utils старше griffincss.ui,
поэтому любая утилита перекрывает компонент без !important
и без гонки специфичностей.
<div class="gr-flex gr-gap">
<button class="gr-btn gr-btn-primary gr-flex-1">Сохранить</button>
<button class="gr-btn">Отмена</button>
</div>
Слот под иконку
Своего набора иконок Griffincss не везёт и не будет: набор — отдельный
продукт со своим темпом обновления и своей лицензией. Вместо набора
библиотека даёт слот и обещание, что иконка в нём встанет правильно
с любым набором. Отдельного класса .gr-icon для этого
не нужно — и это не экономия, а проверенный результат: всё, что слот
должен делать, уже делают сами компоненты.
Что обещает библиотека
| Обещание | Чем обеспечено |
|---|---|
| Иконку не нужно выравнивать | .gr-btn, .gr-badge, .gr-menu-item, .gr-nav-link — это flex с align-items: center и своим gap |
| Зазор между иконкой и текстом задан | gap: var(--gr-ui-gap); у плашки — половина от него |
| Иконка следует цвету текста | цвет наследуется; иконка обязана объявить fill="currentColor" или stroke="currentColor" |
| Иконка следует кеглю | для глифа и иконочного шрифта — сама собой, это текст |
| Готовая коробка 1,25 em | .gr-alert-icon, .gr-toast-icon, .gr-menu-icon — flex: none и фиксированный квадрат |
| Квадратная кнопка под одну иконку | .gr-btn-icon; требует aria-label |
Чего библиотека ждёт от иконки
| Требование | Почему |
|---|---|
Инлайновый <svg> объявляет свой размер сам | SVG без размеров берёт свои 300×150 px; утилиты размера в em в библиотеке нет, а .gr-w-[1em] — произвольное значение и требует подключённого griffincss.js |
| Декоративная иконка скрыта от скринридера | aria-hidden="true": иначе читалка озвучит её вместе с текстом кнопки |
Иконка без текста несёт aria-label | у кнопки-иконки другого имени нет |
| Иконка — не единственный признак | то же правило, что у цвета: значок без подписи угадывается, а не читается |
Выравнивание по базовой линии внутри компонента утилитой
не задаётся. .gr-flex-items-baseline живёт в слое
griffincss.core, а align-items: center
компонента — в слое griffincss.ui, который идёт позже
и потому выигрывает. Ловушка та же, что у видимости: побеждает
не специфичность, а порядок слоёв. Практического неудобства тут нет —
в кнопке, плашке и пункте меню нужно ровно центрирование, — но знать
об этом стоит до того, как класс будет добавлен и молча не сработает.
Два набора, ни одного глифа в репозитории
Примеры ниже показывают два распространённых способа подключить чужой набор. Оба — без зависимости от Griffincss и без единого значка в его составе: библиотека описывает слот, а не заполняет его.
Иконочный шрифт. Значок — символ, поэтому кегль
и цвет он берёт сам, а от разметки ему нужно только имя класса набора
и aria-hidden.
<button class="gr-btn gr-btn-primary">
<i class="bi bi-download" aria-hidden="true"></i>
Скачать
</button>
<button class="gr-btn gr-btn-icon" aria-label="Настройки">
<i class="bi bi-gear" aria-hidden="true"></i>
</button>
Спрайт из <svg>. Размер объявляется
атрибутами, цвет — currentColor. Атрибуты, а не класс:
без них SVG развернётся во все свои 300×150 px, и произойдёт это
ещё до того, как выполнится любой скрипт.
<button class="gr-btn">
<svg width="16" height="16" fill="currentColor" aria-hidden="true">
<use href="icons.svg#download"></use>
</svg>
Скачать
</button>
gap кнопкиТри размера кнопки в этом ряду — проверка обещания «иконка следует кеглю»: ни на одной из них у значка нет своего класса размера.
Примеры использования
Кнопка почти всегда стоит в компании: пара CTA в hero, ряд действий формы, слитая группа-переключатель. Рецепты ниже — три самые частые компании и роли в них; состояние загрузки разобрано на странице Transitions.
Пара CTA: главная и вторая
Первый экран продаёт одно действие — залитая .gr-btn-primary,
а второй даёт запасной выход — тихая .gr-btn-ghost
со стрелкой. Иерархия строится контрастом заливки, а не размером: обе
кнопки .gr-btn-lg и стоят в одном флекс-ряду. На мобильном
ряд складывается и кнопки растягиваются: .gr-flex-col
.gr-flex-row-sm на контейнере, .gr-w-full .gr-w-auto-sm
на кнопках.
<div class="gr-flex gr-flex-col gr-flex-row-sm gr-gap-sm gr-flex-center">
<button class="gr-btn gr-btn-primary gr-btn-lg gr-w-full gr-w-auto-sm">Попробовать бесплатно</button>
<button class="gr-btn gr-btn-ghost gr-btn-lg gr-w-full gr-w-auto-sm">Как это работает →</button>
</div>
Кнопка с иконкой и ряд действий
Иконка уточняет глагол: «⬇ Скачать», «+ Добавить». Кнопка уже
выравнивает содержимое по центру — иконке хватает быть первым символом
или span-ом внутри. В ряду действий формы у каждой кнопки
своя роль: опасное действие уводится в начало строки и в .gr-btn-ghost
.gr-text-danger, пара «Отмена / Сохранить» дожимается к концу строки
через .gr-ms-auto — приём авто-маржина
из Spacing.
<div class="gr-flex gr-flex-wrap gr-gap-sm gr-flex-items-center">
<button class="gr-btn gr-btn-ghost gr-text-danger">Удалить черновик</button>
<button class="gr-btn gr-ms-auto">Отмена</button>
<button class="gr-btn gr-btn-primary">⬇ Сохранить</button>
</div>
Группа-переключатель вида
«Список / сетка», «День / Неделя / Месяц» — слитая группа
.gr-btn-group: скругления только на внешних углах, границы
не удваиваются. Активный сегмент отмечается и стилем
(.gr-btn-primary), и семантикой — aria-pressed="true":
скринридеру заливка не видна. Капсульный вариант той же задачи —
сегмент-контрол — разобран на странице
Border Radius.
<div class="gr-btn-group" role="group" aria-label="Вид списка">
<button class="gr-btn gr-btn-primary" aria-pressed="true">Список</button>
<button class="gr-btn" aria-pressed="false">Сетка</button>
<button class="gr-btn" aria-pressed="false">Канбан</button>
</div>
aria-pressed