GrSwitch

  • ядро
  • Формы
  • API · 19 props
  • ⌨ клавиши

Берут, когда настройка включается сразу.

import { GrSwitch } from '@feugene/granularity/components/GrSwitch'

Обзор

Когда брать

  • настройка включается сразу — уведомления, тёмная тема, доступ по ссылке: кнопки «Сохранить» рядом нет;
  • применение идёт на сервер — loading держит состояние на время запроса, не давая переключить дважды;
  • состояние важнее выбора — «включено/выключено» читается быстрее пары вариантов;
  • значение не булево — value задаёт, что уходит наружу во включённом положении.

Когда взять другое

НужноБерите
Значение уедет с отправкой формыGrCheckbox
Вариантов два, но это режимы отображенияGrSegmented
Вариантов больше двухGrRadioGroup
Значений несколькоGrCheckboxGroup

Playground контролы выводятся из API, а не пишутся руками

Сцена

Загружается…

0 изменено· состояние — в адресе

Примеры 3 у каждого — живое демо и его код

  • Уведомления, которые применяются сразу

    Переключатель — для изменения, которое происходит сразу: «Сохранить» внизу нет. :model-value с @update:model-value двигают его, только когда сервер согласен; пока запрос в пути, loading крутит спиннер в бегунке, не даёт переключить второй раз и через loading-text говорит, что сохраняется. Дайджест не сохраняется и остаётся выключенным. label-position="start" ставит текст первым, уведомления о спорах — disabled.

  • Флаги функций по окружениям

    В колонке флагов состояние должно читаться само по себе: show-state-text пишет его внутри дорожки. Флаг, удалённый из кода, — readonly: виден и фокусируется, но отсюда не переключается. Аварийный выключатель окрашен через active-background-color, а слова на нём берут подходящий цвет через --gr-switch-state-text-fg.

    FlagStagingProduction
    proration-v2New proration for seats added mid-cycle
    sepa-instantSEPA Instant payouts
    legacy-vatOld VAT lookup — removed in 4.2
    Maintenance modeCheckout shows «Back in a few minutes»; webhooks are queued.
  • Отчёт по ссылке

    auto-width растит дорожку под «Public» и «Private», не прыгая между ними, а слоты checked-icon и unchecked-icon кладут знак на бегунок. Переключатель стоит вне формы, и form привязывает его к ней; включённый отправляет name=value, выключенный — ничего.

    Revenue · Q3 2026Owner: Mina Ortiz
    Only members of the workspace can open the report.

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
disabledboolean | undefinedfalseНедоступен: не переключается и в форму не уходит. Складывается по «или» с полем и формой.
modelValuev-modelboolean | undefinedfalseЗначение переключателя. Необязательное: без `v-model` контрол рисуется выключенным — так же, как `GrCheckbox`. Обязательный проп заставлял бы заводить `ref` даже там, где переключатель presentational.
readonlyboolean | undefinedfalseТолько для чтения: состояние видно, но не переключается.
invalidboolean | undefinedfalseВизуальное и ARIA-состояние ошибки.
requiredboolean | undefinedfalseОбязательное поле (`aria-required`).
size"xs" | "sm" | "md" | "lg" | undefinedundefinedРазмер дорожки и бегунка: `xs`–`lg`. По умолчанию `md`; берётся из `GrConfigProvider`.
ariaLabelstring | undefinedundefinedДоступное имя, когда подписи нет ни в слоте, ни у `GrFormField`.
loadingboolean | undefinedfalseИдёт сохранение: бегунок показывает спиннер, переключение заблокировано.
loadingTextstring | undefinedundefinedi18n: что именно грузится. `aria-busy` сам по себе часть AT не объявляет.
namestring | undefinedundefinedИмя поля для нативной отправки формы. Без него скрытое поле не рендерится.
valuestring | undefined"on"Значение, уходящее в форму во включённом состоянии.
formstring | undefinedundefined`id` формы, если переключатель лежит вне неё.
labelPosition"end" | "start" | undefined"end"Сторона подписи относительно дорожки.
activeBackgroundColorstring | undefinedundefinedКастомный цвет фона в активном состоянии. Если не задан — `var(--gr-primary)`.

Слоты

SlotTypeОписание
defaultanyПодпись переключателя.
checked-iconanyЗнак на бегунке во включённом состоянии — галочка, замок, что угодно. Размер задаёт обёртка по ступени `size`, поэтому содержимое слота тянется до неё: `class="h-full w-full"`, как у `GrBadge` и `GrChip`. Знак декоративен — бегунок целиком `aria-hidden`, а состояние диктор берёт из `aria-checked`. Смысла, которого нет больше нигде, сюда класть нельзя.
unchecked-iconanyТо же для выключенного состояния. Слоты независимы.

События

EventСигнатураОписание
update:modelValue(event: "update:modelValue", value: boolean): void—
change(event: "change", value: boolean): void—
focus(event: "focus", event: FocusEvent): void—
blur(event: "blur", event: FocusEvent): void—

Методы

MethodsСигнатураОписание
focus() => voidПоставить фокус на переключатель.
blur() => voidСнять фокус с переключателя.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrSwitchConfigurableProps, GrSwitchSize, GrSwitchLabelPosition } from '@feugene/granularity/components/GrSwitch'

Файлdist/types/components/GrSwitch/config.d.ts

grSwitchConfigconst
const grSwitchConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrSwitch">;

Файлdist/types/components/GrSwitch/defaults.d.ts

GrSwitchConfigurablePropsinterface

Пропы GrSwitch, настраиваемые глобально через <GrConfigProvider :component-defaults="{ GrSwitch: { … } }">. Только оформление — см. GrButton/defaults.ts.

interface GrSwitchConfigurableProps {
    size: GrSwitchSize;
    showStateText: boolean;
    autoWidth: boolean;
}

Файлdist/types/components/GrSwitch/safelist.d.ts

grSwitchSafelistconst
const grSwitchSafelist: string[];

Файлdist/types/components/GrSwitch/grSwitchStyles.d.ts

GrSwitchSizetype
type GrSwitchSize = GrComponentSize;
GrSwitchLabelPositiontype
type GrSwitchLabelPosition = typeof GR_SWITCH_LABEL_POSITIONS[number];
GrSwitchStateTextSizetype
type GrSwitchStateTextSize = typeof GR_SWITCH_STATE_TEXT_SIZES[number];

Файлdist/types/components/GrSwitch/GrSwitch.vue.d.ts

GrSwitchPropsinterface

Пропсы публичного GR-примитива «Switch».

interface GrSwitchProps {
    /**
     * Значение переключателя. Необязательное: без `v-model` контрол рисуется
     * выключенным — так же, как `GrCheckbox`. Обязательный проп заставлял бы
     * заводить `ref` даже там, где переключатель presentational.
     */
    modelValue?: boolean;
    /** Недоступен: не переключается и в форму не уходит. Складывается по «или» с полем и формой. */
    disabled?: boolean;
    /** Только для чтения: состояние видно, но не переключается. */
    readonly?: boolean;
    /** Визуальное и ARIA-состояние ошибки. */
    invalid?: boolean;
    /** Обязательное поле (`aria-required`). */
    required?: boolean;
    /** Доступное имя, когда подписи нет ни в слоте, ни у `GrFormField`. */
    ariaLabel?: string;
    /** Размер дорожки и бегунка: `xs`–`lg`. По умолчанию `md`; берётся из `GrConfigProvider`. */
    size?: GrSwitchSize;
    /** Сторона подписи относительно дорожки. */
    labelPosition?: GrSwitchLabelPosition;
    /** Идёт сохранение: бегунок показывает спиннер, переключение заблокировано. */
    loading?: boolean;
    /** i18n: что именно грузится. `aria-busy` сам по себе часть AT не объявляет. */
    loadingText?: string;
    /** Имя поля для нативной отправки формы. Без него скрытое поле не рендерится. */
    name?: string;
    /** Значение, уходящее в форму во включённом состоянии. */
    value?: string;
    /** `id` формы, если переключатель лежит вне неё. */
    form?: string;
    /** Кастомный цвет фона в активном состоянии. Если не задан — `var(--gr-primary)`. */
    activeBackgroundColor?: string;
    /** Кастомный цвет фона в неактивном состоянии. Если не задан — `var(--gr-muted)`. */
    inactiveBackgroundColor?: string;
    /**
     * Подпись состояния внутри дорожки — на свободной от бегунка стороне.
     * Помещается только на `md` и `lg`; на мелких ступенях не рисуется.
     */
    showStateText?: boolean;
    /** Текст включённого состояния. Без него — `gr.switch.on` из локали. */
    checkedText?: string;
    /** Текст выключенного состояния. Без него — `gr.switch.off` из локали. */
    uncheckedText?: string;
    /**
     * Дорожка растягивается под подпись, а ступень размера становится нижней
     * границей ширины. Без `showStateText` смысла не имеет: тянуться не под что.
     */
    autoWidth?: boolean;
}
GrSwitchEmitsinterface
interface GrSwitchEmits {
    /** Новое состояние для `v-model`. При `disabled`, `readonly` и `loading` не шлётся. */
    (e: 'update:modelValue', value: boolean): void;
    /** Пользователь переключил — тот же payload, что у `update:modelValue`. */
    (e: 'change', value: boolean): void;
    /** Фокус пришёл на переключатель — в том числе кликом по подписи `GrFormField`. */
    (e: 'focus', event: FocusEvent): void;
    /** Фокус ушёл с переключателя. */
    (e: 'blur', event: FocusEvent): void;
}

Файлdist/types/components/GrSwitch/index.d.ts

GrSwitchInstancetype
type GrSwitchInstance = ComponentExposed<typeof GrSwitchComponent>;

Доступность

Паттерн APG

switch

Клавиши

Enter

КлавишаДействие
—Space
Enterпереключить (нативная <button role="switch">)

Использование из прозы библиотеки

Нативная форма
vue
<GrSwitch v-model="notifications" name="notifications" value="on">
  Email-уведомления
</GrSwitch>

Семантика чекбокса: включённый переключатель отправляет name=value, выключенный не отправляется вовсе — сервер отличает «выкл» по отсутствию ключа. Без name скрытого поля нет; form связывает переключатель с формой, если он лежит вне неё.

Внутри <button> интерактивный контент невалиден, поэтому скрытое поле — сосед кнопки, а корень компонента — фрагмент. class и прочие атрибуты по-прежнему садятся на кнопку.

Загрузка
vue
<GrSwitch :model-value="backup" :loading="syncing" @change="save" />

loading показывает спиннер в бегунке, помечает контрол aria-busy и блокирует переключение, пока запрос в полёте. Что именно грузится, задаёт loadingText (по умолчанию — ключ gr.switch.loading): aria-busy сам по себе часть скринридеров не объявляет.

Подпись

Подпись приходит слотом по умолчанию, labelPosition="start" переносит её влево от дорожки. Ряд именно разворачивается, а не переставляется в DOM: диктор читает порядок узлов, и дорожка должна оставаться первой.

Без подписи имя обязателен задать иначе — ariaLabel или GrFormField, который свяжет <label for> с кнопкой.

Подпись состояния живёт не на всех ступенях
vue
<GrSwitch v-model="notifications" show-state-text>
  Уведомления
</GrSwitch>

showStateText ставит «вкл/выкл» внутрь дорожки, на свободную от бегунка сторону: состояние читается само по себе, без сравнения с соседним переключателем. Тексты берутся из локали (gr.switch.on / gr.switch.off) и перекрываются пропами checkedText / uncheckedText.

Подпись рисуется только на md и lg, на xs и sm её нет, а в dev-сборке компонент объясняет почему. Дело в геометрии: рядом с бегунком остаётся 13 / 17 / 21 / 29px на ступенях xs…lg, а трёхбуквенное слово занимает около 19px даже на 9px кегля. Дорожка при этом не растягивается под текст — её ширина фиксирована таблицей размеров, и от неё же считается ход бегунка.

Отсюда же и русский дефолт «ДА» / «НЕТ»: «ВЫКЛ» занимает 31px и не помещается ни на одной ступени. Собственный текст стоит мерить так же — длинный молча обрежется.

Для диктора подписи нет вовсе (aria-hidden): дорожка лежит внутри <button role="switch">, и видимый текст вошёл бы в доступное имя — «Wi-Fi ON» вместо «Wi-Fi», хотя состояние уже объявлено через aria-checked.

Цвет подписи — роль -fg той заливки, на которой она лежит. Задали дорожке свой цвет через activeBackgroundColor — задайте и цвет текста: контраст на произвольной заливке библиотека не вычислит. Оба хука (цвет и кегль) — в ../tokens.md.

Знак на бегунке
vue
<GrSwitch v-model="autosave">
  <template #checked-icon>
    <IconCheck class="h-full w-full" />
  </template>
  <template #unchecked-icon>
    <IconX class="h-full w-full" />
  </template>
  Автосохранение
</GrSwitch>

Слоты checked-icon и unchecked-icon кладут знак на сам бегунок. Размер держит обёртка по ступени size, а содержимое тянется до неё (h-full w-full) — тот же приём, что у GrBadge и GrChip, и по той же причине: шкала знака обязана идти за переключателем, а не за разметкой места вызова.

Слоты независимы: нужен знак только во включённом состоянии — второй просто не передаётся.

Знаку доступны все ступени, включая xs и sm, где подпись состояния в дорожке не помещается: знак ужимается без потери узнаваемости, в отличие от трёхбуквенного слова. Внутри бегунка ему остаётся 8 / 10 / 12 / 16px.

loading вытесняет знак. Спиннер и знак занимают один центр, и пока идёт запрос, важнее «состояние меняется», чем «каким оно стало».

Знак декоративен: бегунок целиком aria-hidden, а состояние диктор берёт из aria-checked. Смысла, которого нет больше нигде, сюда класть нельзя.

Цвет знака и спиннера — общий, из --gr-switch-thumb-fg. По состоянию он не ветвится и в disabled не тускнеет: гасится дорожка, а бегунок остаётся --gr-card в любом состоянии, и содержимое, меняющее цвет, разошлось бы со своей же подложкой.

Дорожка растягивается только по просьбе
vue
<GrSwitch v-model="access" show-state-text auto-width checked-text="Включено" unchecked-text="Отключено">
  Доступ по ссылке
</GrSwitch>

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

Дефолт остаётся фиксированным намеренно. Ряд переключателей с разными подписями становится рваным, а тот, кто сверстал колонку под ширину ступени, получит перекос — это выбор потребителя, а не поведение по умолчанию.

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

Бегунок в обоих режимах ходит без единого числа, зависящего от ширины: пара left: calc(100% − зазор) и translateX(-100%) считает проценты от разных элементов — первый от дорожки, второй от самого бегунка. Поэтому растягивание не требует ни измерений, ни ResizeObserver, а ход остаётся линейным: обе величины анимируются вместе.

Состояния
ПропЧто делает
disabledгасит контрол токенами --gr-disabled-bg / -brd / -fg; они перебивают и кастомные цвета дорожки
readonlyсостояние видно, но не меняется (aria-readonly)
invalid, requiredaria-invalid / aria-required; складываются с контекстом GrFormField

Недоступность гасится фоном, а не opacity: прозрачность разбавляет выверенные на AA токены текста и роняет контраст подписи.

События и клавиатура

update:modelValue и change эмитятся вместе. Экземпляр отдаёт focus() и blur().

Клавиатура нативная: контрол — настоящая <button>, поэтому Space и Enter переключают его силами браузера, а Tab обходит в общем порядке.

Оформление

size — xs…lg, читается из GrConfigProvider (componentDefaults.GrSwitch.size). Цвет дорожки точечно задаётся пропами activeBackgroundColor / inactiveBackgroundColor.