GrAlert
Берут, когда сообщение относится к месту на странице.
Когда брать
- сообщение относится к месту на странице — предупреждение над формой, пояснение в разделе: оно живёт там же, где причина;
- сообщение обязано остаться — тост уйдёт сам, а это читают столько, сколько нужно;
- внутри есть действие — слот действий держит «Повторить», «Перейти к настройкам»;
- сообщение появляется асинхронно —
liveобъявляет его скринридеру, а не оставляет молча.
Когда взять другое
| Нужно | Берите |
|---|---|
| Сообщение о результате действия, короткое | GrToaster |
| Ошибка ответа сервера со статусом и деталями | GrResponseErrorBanner |
| Ошибка одного поля | GrFormField |
| На экране пусто, и надо объяснить почему | GrEmptyState |
| Требуется ответ пользователя | GrConfirmDialog |
Тон и вариант — разные оси
tone отвечает за цвет и смысл (info, success, warning, danger,
slate, azure), variant — за вес: soft (тонированная подложка) или
outline (только рамка). Любая комбинация осмысленна.
Цвета выражены токенами --gr-* целиком, поэтому сообщение одинаково корректно
в светлой и тёмной теме. Точечная подгонка — пропы backgroundColor,
textColor, borderColor либо переменные --gr-alert-* (см. tokens.md).
Как сообщение объявляется скринридеру
Ключевая роль здесь у live, и по умолчанию она выведена из тона:
live | Что получает элемент | Когда |
|---|---|---|
auto (по умолчанию) | alert для warning/danger, status для остальных | обычный случай |
assertive | role="alert" — перебивает чтение | сообщение требует немедленной реакции |
polite | role="status" — дождётся паузы | сообщение можно дочитать позже |
off | роли нет вовсе | сообщение уже объявлено другим способом |
Смысл дефолта: role="alert" прерывает речь, и вешать его на каждое
информационное сообщение значит превращать спокойную подсказку в тревогу.
Режим настраивается глобально — приложению, которому алерты не должны перебивать речь, не нужно ставить проп на каждый:
<GrConfigProvider :component-defaults="{ GrAlert: { live: 'polite' } }">
Через componentDefaults задаются также tone, variant и closable.
Иконка
По умолчанию глиф выбирается по тону. Слот #icon подменяет его, проп
:icon="false" убирает:
<GrAlert tone="success">
<template #icon>
<IconRocket />
</template>
Deploy finished.
</GrAlert>
<GrAlert tone="slate" :icon="false">
Draft saved automatically.
</GrAlert>
Иконка декоративна в обоих случаях — она остаётся aria-hidden, потому что
смысл несёт текст, а не картинка. Сообщение без иконки читается спокойнее и
уместно там, где алертов много: плотная форма, список настроек.
Действия
Кнопки «Повторить», «Подробнее» кладутся в слот #actions, а не в текст: они
получают своё место под сообщением и не разрывают фразу.
<GrAlert tone="danger" title="Export failed" closable>
The report service returned 502.
<template #actions>
<GrButton size="sm" tone="danger" @click="retry">Retry</GrButton>
<GrButton size="sm" variant="outline" tone="danger">Open logs</GrButton>
</template>
</GrAlert>Закрытие: `close` против `v-model:visible`
closable добавляет кнопку. Дальше — выбор потребителя:
<!-- сообщение остаётся, пока экран не решит иначе -->
<GrAlert closable @close="askConfirmation" />
<!-- сообщение прячет себя само -->
<GrAlert v-model:visible="shown" closable />
Собственного состояния у visible нет намеренно: без пропа алерт не
исчезает по клику, а только сообщает о намерении событием close. Иначе
кнопка закрытия стала бы необратимым действием у всех, кто по close
спрашивает подтверждение или пишет отметку на сервер. close эмитится в обоих
режимах.
Смежное
theming.md— роли цвета и суффиксы.tokens.md— переменные--gr-alert-*.
Playground 9
Загружается…
<GrAlert />Установка
npm i @feugene/granularityИмпорт
import { GrAlert } from '@feugene/granularity/components/GrAlert'API
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
tone | "primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined | undefined | — |
variant | "soft" | "outline" | undefined | undefined | — |
title | string | undefined | undefined | — |
closable | boolean | undefined | undefined | — |
live | GrAlertLive | undefined | undefined | — |
icon | boolean | undefined | undefined | Показывать ли иконку тона. Своя иконка — слотом `#icon`; проп нужен для случая «иконка мешает»: узкая колонка, плотный список, сообщение в форме. |
visible | boolean | undefined | undefined | Видимость сообщения — **только контролируемая**: без пропа алерт виден всегда, и закрытие остаётся заботой потребителя (`@close`). С `v-model:visible` компонент прячет себя сам. Собственного состояния у пропа нет намеренно. Оно превратило бы кнопку закрытия в необратимое действие у всех, кто уже живёт на `@close` и, например, спрашивает по нему подтверждение. |
backgroundColor | string | undefined | undefined | — |
textColor | string | undefined | undefined | — |
borderColor | string | undefined | undefined | — |
Slots
| Slot | Type | Описание |
|---|---|---|
default | any | Текст сообщения. |
icon | any | Иконка вместо глифа по тону. Декоративна: остаётся `aria-hidden`. |
actions | any | Действия по сообщению: «Повторить», «Подробнее». Ложатся под текст. |
Events
| Event | Type | Описание |
|---|---|---|
close | [] | — |
update:visible | [value: boolean] | — |
Примеры 4
Действия, самозакрытие и сообщение без иконки
Слот actions даёт кнопкам своё место под текстом, v-model:visible позволяет алерту скрыть себя, а :icon="false" убирает глиф там, где сообщение должно звучать спокойно.
<script setup lang="ts">
import { ref } from 'vue'
import { GrAlert, GrButton } from '@feugene/granularity'
const visible = ref(true)
const attempts = ref(0)
</script>
<template>
<div class="grid gap-4">
<GrAlert
v-model:visible="visible"
tone="danger"
title="Export failed"
closable
>
The report service returned 502 while building «Q3 revenue».
<template #actions>
<GrButton size="sm" tone="danger" @click="attempts++">
Retry
</GrButton>
<GrButton size="sm" variant="outline" tone="danger">
Open logs
</GrButton>
</template>
</GrAlert>
<div v-if="!visible" class="flex items-center gap-3">
<span class="text-sm text-[var(--gr-muted-fg)]">Alert dismissed itself.</span>
<GrButton size="sm" variant="outline" @click="visible = true">
Bring it back
</GrButton>
</div>
<GrAlert tone="slate" :icon="false">
Retried {{ attempts }} time(s). Without an icon the message reads as a plain note —
useful in dense forms where every alert would otherwise shout.
</GrAlert>
</div>
</template>Семантические тона для сообщений в потоке
Базовая матрица фиксирует ключевые alert-tone состояния, чтобы на странице компонента сразу был виден визуальный диапазон info/success/warning/danger/slate/azure.
<script setup lang="ts">
import { GrAlert } from '@feugene/granularity'
</script>
<template>
<div class="grid gap-3">
<GrAlert title="Info" tone="info">
Deploy preview URL is ready for the QA handoff.
</GrAlert>
<GrAlert title="Success" tone="success">
Billing sync finished and no manual retries are required.
</GrAlert>
<GrAlert title="Warning" tone="warning">
API quota is at 78%; consider moving heavy jobs to the night window.
</GrAlert>
<GrAlert title="Danger" tone="danger">
Background worker lost connection to Redis and needs operator attention.
</GrAlert>
<GrAlert title="Slate" tone="slate">
Runbook is archived and kept for passive operator context.
</GrAlert>
<GrAlert title="Azure" tone="azure">
Release note references are ready for stakeholder review.
</GrAlert>
</div>
</template>Закрываемый алерт с состоянием на стороне экрана
Без v-model:visible алерт себя не прячет: он шлёт close, а родительский экран сам решает, скрыть banner или спросить подтверждение.
<script setup lang="ts">
import { ref } from 'vue'
import { GrAlert, GrButton, GrCard } from '@feugene/granularity'
const visible = ref(true)
</script>
<template>
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_220px]">
<GrAlert
v-if="visible"
title="Maintenance window"
tone="warning"
closable
@close="visible = false"
>
Payments will be processed in read-only mode from 02:00 to 02:30 UTC.
</GrAlert>
<GrCard v-else class="flex min-h-[92px] items-center justify-center p-4 text-sm text-[var(--gr-muted-fg)]">
Alert dismissed. Bring it back from the side panel.
</GrCard>
<GrCard class="grid gap-3 p-4">
<div>
<div class="text-sm font-semibold text-[var(--gr-fg)]">
Close event
</div>
<div class="text-sm text-[var(--gr-muted-fg)]">
`close` is emitted so the host screen can hide or persist the banner state.
</div>
</div>
<GrButton size="sm" variant="outline" :disabled="visible" @click="visible = true">
Restore alert
</GrButton>
</GrCard>
</div>
</template>Фирменные цвета без правки раскладки
Сценарий нужен для dashboard-команд, которым важно подстроить alert под доменный бренд, но сохранить icon/layout API компонента.
<script setup lang="ts">
import { GrAlert } from '@feugene/granularity'
</script>
<template>
<div class="grid gap-3 lg:grid-cols-2">
<GrAlert
title="Custom brand banner"
background-color="#ecfeff"
border-color="#22d3ee"
text-color="#155e75"
>
Teams often override colors to align alerts with domain-specific dashboards or tenant branding.
</GrAlert>
<GrAlert
title="Muted reminder"
background-color="#f8fafc"
border-color="#cbd5e1"
text-color="#334155"
>
The component still keeps the same layout, icon slotting and close mechanics while colors are fully customized.
</GrAlert>
</div>
</template>