GrButton
Берут, когда действие меняет состояние.
Обзор
Когда брать
- действие меняет состояние — сохранить, удалить, отправить: адрес при этом не меняется;
- действие идёт на сервер —
loadingблокирует повтор и объявляет ожидание; - важность действия видна —
variantиtoneразводят главное, второстепенное и опасное; - кнопка только из иконки —
squareвместе сariaLabel: без имени такая кнопка безымянна; - нужна ссылка с видом кнопки —
href/asподменяют тег, оставляя семантику ссылки.
Когда взять другое
| Нужно | Берите |
|---|---|
| Переход меняет адрес | GrLink |
| Действий несколько в ряд | GrButtonGroup |
| Действий много, они прячутся | GrDropdownMenu |
| Переключается состояние «включено/выключено» | GrSwitch |
| Переключается вид одного содержимого | GrSegmented |
Варианты все тона и виды — настоящим компонентом
tone \ variant | primary | secondary | outline | ghost | ghost-border |
|---|---|---|---|---|---|
primary | |||||
neutral | |||||
success | |||||
warning | |||||
danger | |||||
info | |||||
slate | |||||
azure |
Размеры
xssmmdlg
Состояния
обычнаяdisabledloading
Матрица живая: наводите и нажимайте. Состояний наведения, фокуса и нажатия здесь нет — они за браузером, а не за пропом.
Playground контролы выводятся из API, а не пишутся руками
Загружается…
0 изменено· состояние — в адресе
<GrButton>Save</GrButton>Примеры 4 у каждого — живое демо и его код
Панель с одним громким действием
Второстепенные инструменты —
variant="ghost-border": тихие в ряду, с рамкой под указателем. Слотыprefixиsuffixдержат иконку, число фильтров и сочетание клавиш, и подпись с ними не сталкивается. Квадратная (square) кнопка из одной иконки получает имя изaria-label; залита только та, что создаёт.InvoicesNorthwind TradersINV-2026-0418 · Overdue€5,166.50Contoso LtdINV-2026-0417 · Paid€820.00Tailspin ToysINV-2026-0416 · Sent€310.00Сохранить и отменить в подвале формы
type="submit"иtype="reset"отдают работу форме: Enter сохраняет, а сброс перехватывается и возвращает сохранённое. Пока идёт запрос,loadingоставляет кнопку в фокусе и не пускает второй клик, аloading-textговорит скринридеру, что сохраняется. Обе кнопки ждут изменений — пока сохранять и отменять нечего.Разрушительное действие — через осознанный шаг
tone="danger"помечает то, что не отменить, а кнопка с контуром только открывает шаг. Последняя кнопка остаётсяdisabled, пока не набрано имя, занимает всю ширину черезblockи показываетloading, пока пространство удаляется. Отмена возвращает клавиатуру на кнопку-триггер через открытый методfocus().Danger zone Transfer ownershipHand the workspace to another admin.Delete workspaceInvoices, customers and 12 members lose access.Кнопки, которые на самом деле ссылки
Каждый шаг куда-то ведёт, поэтому каждая кнопка — ссылка с видом кнопки.
asберёт ссылку роутера для маршрута внутри приложения,hrefрисует обычный<a>, именованныйtargetсrelпереиспользует одну вкладку документации, аexternalсам открывает новую вкладку с безопаснымrel. Ссылка сdisabledуходит из порядка Tab и остаётся приглушённой — нативногоdisabledу ссылок нет.Launch checklistEvery step goes somewhere, so every button here is a link. - 1. Invite your teamInvite people
- 2. Read the getting-started guideOpen the guide
- 3. Look through the sourceGitHub
- 4. Connect a payment providerOnly an owner can do this.Connect
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
disabled | boolean | undefined | false | Действие недоступно: нативный `disabled` у `<button>`, у кнопки-ссылки — `aria-disabled` и снятый адрес. Цвета — из токенов, без прозрачности. |
type | "button" | "submit" | "reset" | undefined | "button" | Нативный `type` у `<button>`. По умолчанию `button`: кнопка в форме не отправляет её сама. |
tone |
| undefined | Смысл действия: цвет заливки, рамки и текста. `danger` — для необратимого, `neutral` — для второстепенного. По умолчанию `primary`; внутри `GrButtonGroup` — тон группы. |
variant | GrButtonVariant | undefined | undefined | Вес кнопки: `primary` — заливка тона, `secondary` — мягкая подложка с рамкой, `outline` — только рамка, `ghost` — только текст, `ghost-border` — текст, рамка которого проявляется под указателем. По умолчанию `primary`; глобально — `componentDefaults`. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | Высота и кегль по шкале контролов. По умолчанию `md`; берётся из группы и `GrConfigProvider`. |
ariaLabel | string | undefined | undefined | Доступное имя кнопки, когда текста нет: `square` с одной иконкой без него безымянна. |
loading | boolean | undefined | false | Действие выполняется: спиннер на месте префикса, `aria-busy` и перехват клика. Кнопка при этом остаётся в фокусе — нативный `disabled` не ставится. |
loadingText | string | undefined | undefined | i18n: что именно грузится. `aria-busy` сам по себе часть AT не объявляет. |
square | boolean | undefined | undefined | Равные стороны — кнопка из одной иконки. Без текста ей нужен `ariaLabel`. |
as | string | Component | undefined | undefined | Полиморфизм: кастомный корневой тег/компонент (например, RouterLink). |
block | boolean | undefined | false | Кнопка на всю ширину контейнера. |
href | string | undefined | undefined | Рендерит кнопку как `<a href>` (если не задан `as`). |
target | string | undefined | undefined | `target` кнопки-ссылки. С `_blank` сам добавляется `rel="noopener noreferrer"`. |
rel | string | undefined | undefined | `rel` кнопки-ссылки. Без него при `target="_blank"` ставится `noopener noreferrer`. |
external | boolean | undefined | false | Открывать ссылку в новой вкладке: `target="_blank"` и `rel="noopener noreferrer"`. |
Слоты
| Slot | Type | Описание |
|---|---|---|
default | any | Содержимое кнопки. |
prefix | any | Аддон слева: иконка, счётчик. В состоянии загрузки уступает место спиннеру. |
suffix | any | Аддон справа: шеврон, счётчик. |
Методы
| Methods | Сигнатура | Описание |
|---|---|---|
focus | () => void | Поставить фокус на кнопку — и когда корень тег, и когда компонент из `as`. |
blur | () => void | Снять фокус с кнопки. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrButtonConfigurableProps, GrButtonVariant, GrButtonTone } from '@feugene/granularity/components/GrButton'Файлdist/types/components/GrButton/config.d.ts
const grButtonConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrButton">;Файлdist/types/components/GrButton/defaults.d.ts
Пропы GrButton, которые можно задать глобально через
<GrConfigProvider :component-defaults="{ GrButton: { … } }">.
Набор закрытый и осознанный: только оформление. modelValue, обработчики и
всё, что относится к конкретному экземпляру, через конфиг настраиваться не
должно — иначе получим глобальное состояние, которое ищется часами.
interface GrButtonConfigurableProps {
variant: GrButtonVariant;
tone: GrButtonTone;
size: GrButtonSize;
square: boolean;
}Файлdist/types/components/GrButton/grButtonStyles.d.ts
type GrButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'ghost-border';type GrButtonTone = GrTone;type GrButtonSize = GrComponentSize;Файлdist/types/components/GrButton/safelist.d.ts
Только матрица тон × вариант: grButtonStyles.ts: variantClass клеит каждый
её класс из префикса и токена тона (bg-${withVar(tokens.solidBackground)}),
и целиком их нет ни в одной строке кода. Тон и вариант приходят пропом, из
GrButtonGroup или GrConfigProvider, поэтому объявлена вся матрица — 8 × 5.
Собранное узнаётся по скобкам, которые подставляет withVar. Литеральные
куски той же функции (bg-transparent, border, border-transparent), база,
размеры, квадрат, block и отключённое состояние лежат строками целиком —
granum извлекает их из чанков компонента сам.
Ту же матрицу рисует вид button у GrRadio (через grButtonClass): к нему
она приезжает зависимостью GrButton вместе со всеми классами её чанка.
const grButtonSafelist: string[];Файлdist/types/components/GrButton/GrButton.vue.d.ts
interface GrButtonProps {
/**
* Вес кнопки: `primary` — заливка тона, `secondary` — мягкая подложка с
* рамкой, `outline` — только рамка, `ghost` — только текст, `ghost-border` —
* текст, рамка которого проявляется под указателем. По умолчанию `primary`;
* глобально — `componentDefaults`.
*/
variant?: GrButtonVariant;
/**
* Смысл действия: цвет заливки, рамки и текста. `danger` — для необратимого,
* `neutral` — для второстепенного. По умолчанию `primary`; внутри
* `GrButtonGroup` — тон группы.
*/
tone?: GrButtonTone;
/** Высота и кегль по шкале контролов. По умолчанию `md`; берётся из группы и `GrConfigProvider`. */
size?: GrButtonSize;
/**
* Действие выполняется: спиннер на месте префикса, `aria-busy` и перехват
* клика. Кнопка при этом остаётся в фокусе — нативный `disabled` не ставится.
*/
loading?: boolean;
/** i18n: что именно грузится. `aria-busy` сам по себе часть AT не объявляет. */
loadingText?: string;
/**
* Действие недоступно: нативный `disabled` у `<button>`, у кнопки-ссылки —
* `aria-disabled` и снятый адрес. Цвета — из токенов, без прозрачности.
*/
disabled?: boolean;
/** Равные стороны — кнопка из одной иконки. Без текста ей нужен `ariaLabel`. */
square?: boolean;
/** Кнопка на всю ширину контейнера. */
block?: boolean;
/** Нативный `type` у `<button>`. По умолчанию `button`: кнопка в форме не отправляет её сама. */
type?: 'button' | 'submit' | 'reset';
/** Доступное имя кнопки, когда текста нет: `square` с одной иконкой без него безымянна. */
ariaLabel?: string;
/** Полиморфизм: кастомный корневой тег/компонент (например, RouterLink). */
as?: string | Component;
/** Рендерит кнопку как `<a href>` (если не задан `as`). */
href?: string;
/** `target` кнопки-ссылки. С `_blank` сам добавляется `rel="noopener noreferrer"`. */
target?: string;
/** `rel` кнопки-ссылки. Без него при `target="_blank"` ставится `noopener noreferrer`. */
rel?: string;
/** Открывать ссылку в новой вкладке: `target="_blank"` и `rel="noopener noreferrer"`. */
external?: boolean;
}Файлdist/types/components/GrButton/index.d.ts
type GrButtonInstance = ComponentExposed<typeof GrButtonComponent>;Использование из прозы библиотеки
disabled и loading — разные состояния
disabled ставит нативный атрибут (у <button>) и гасит кнопку токенами
--gr-button-disabled-bg / -fg / -brd. Прозрачности здесь нет намеренно:
opacity разбавляет выверенные на AA цвета, а кнопка чаще других компонентов
стоит на цветной подложке, где разбавленный текст проваливается первым.
Сами эти токены — ссылки на общие роли --gr-disabled-bg/-fg/-brd, те же, что у
вкладки, сегмента и бегунка: недоступное состояние во всём пакете выглядит
одинаково и перекрашивается одним местом. Покомпонентная тройка остаётся точкой
кастомизации на случай, когда кнопка обязана отличаться.
Приглушение работает и для кнопки-ссылки (href/as): нативный disabled ей
не достаётся, поэтому цвета задаются классами, а не disabled:-вариантом.
Кнопку, которая должна остаться в таб-порядке, но объявлена недоступной,
помечают атрибутом aria-disabled="true". Она получает тот же приглушённый вид,
нативный disabled не ставится, а клик компонент не перехватывает — его гасит
потребитель. Так GrTransfer держит перенос, которому некуда ехать: уронить
фокус с нажатой кнопки в тело документа было бы хуже.
loading не ставит нативный disabled — элемент остался бы без фокуса, и
скринридер потерял бы контекст. Вместо этого aria-busy, aria-disabled и
перехват клика в capture-фазе. Выглядит кнопка при этом обычной: о состоянии
говорит спиннер.
<GrButton :loading="saving" loading-text="Сохраняем отчёт">
Сохранить
</GrButton>aria-busy сам по себе объявляет не всякая AT, поэтому во время загрузки к
имени кнопки добавляется скрытый суффикс — loadingText или ключ
gr.button.loading.
Слоты
<GrButton>
<template #prefix><GrIcon><IconPlus /></GrIcon></template>
Добавить
<template #suffix><GrKbd keys="mod+N" /></template>
</GrButton>Раньше иконка и текст валились в один слот, и порядок держался дисциплиной
потребителя. Во время loading спиннер занимает место префикса, а #prefix не
рендерится: две иконки рядом читаются как ошибка вёрстки.
Квадратная кнопка и block
square даёт кнопку с равными сторонами — размер приходит из
--gr-button-square-size с дефолтом по размеру (1.75rem / 2rem / 2.5rem /
2.75rem). Переменную можно задать в своём CSS и подогнать кнопку под свою
сетку:
.toolbar { --gr-button-square-size: 2.25rem; }Переменная одна на все четыре ступени. Заданная выше по дереву — на :root,
в теме — она схлопывает xs, sm, md и lg в одно число, и ступень
перестаёт что-либо значить. Поэтому её место — узкий скоуп вроде примера выше, а
не корень документа. Нужен размер по устройству ввода — задавайте его
GrConfigProvider, а не переменной: приложение знает про свой указатель, пакет
про него не знает.
block растягивает кнопку на ширину контейнера.
Полиморфный корень
as → <a href> → <button>. Ссылке автоматически достаётся
rel="noopener noreferrer", если она открывается в новой вкладке (external
или явный target="_blank"), а disabled убирает её из таб-порядка.
Пропы этого примера