GrAlert

Пакет: @feugene/granularityядроГруппа: Обратная связь

Берут, когда сообщение относится к месту на странице.

Когда брать

  • сообщение относится к месту на странице — предупреждение над формой, пояснение в разделе: оно живёт там же, где причина;
  • сообщение обязано остаться — тост уйдёт сам, а это читают столько, сколько нужно;
  • внутри есть действие — слот действий держит «Повторить», «Перейти к настройкам»;
  • сообщение появляется асинхронно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 для остальныхобычный случай
assertiverole="alert" — перебивает чтениесообщение требует немедленной реакции
politerole="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

PropTypeпо умолчаниюОписание
tone"primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefinedundefined
variant"soft" | "outline" | undefinedundefined
titlestring | undefinedundefined
closableboolean | undefinedundefined
liveGrAlertLive | undefinedundefined
iconboolean | undefinedundefinedПоказывать ли иконку тона. Своя иконка — слотом `#icon`; проп нужен для случая «иконка мешает»: узкая колонка, плотный список, сообщение в форме.
visibleboolean | undefinedundefinedВидимость сообщения — **только контролируемая**: без пропа алерт виден всегда, и закрытие остаётся заботой потребителя (`@close`). С `v-model:visible` компонент прячет себя сам. Собственного состояния у пропа нет намеренно. Оно превратило бы кнопку закрытия в необратимое действие у всех, кто уже живёт на `@close` и, например, спрашивает по нему подтверждение.
backgroundColorstring | undefinedundefined
textColorstring | undefinedundefined
borderColorstring | undefinedundefined

Slots

SlotTypeОписание
defaultanyТекст сообщения.
iconanyИконка вместо глифа по тону. Декоративна: остаётся `aria-hidden`.
actionsanyДействия по сообщению: «Повторить», «Подробнее». Ложатся под текст.

Events

EventTypeОписание
close[]
update:visible[value: boolean]

Примеры 4

Действия, самозакрытие и сообщение без иконки

Слот actions даёт кнопкам своё место под текстом, v-model:visible позволяет алерту скрыть себя, а :icon="false" убирает глиф там, где сообщение должно звучать спокойно.

Retried 0 time(s). Without an icon the message reads as a plain note — useful in dense forms where every alert would otherwise shout.

Actions
<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.

Info
Deploy preview URL is ready for the QA handoff.
Success
Billing sync finished and no manual retries are required.
Slate
Runbook is archived and kept for passive operator context.
Azure
Release note references are ready for stakeholder review.

Variants
<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 или спросить подтверждение.

Close event
`close` is emitted so the host screen can hide or persist the banner state.

Closable
<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 компонента.

Custom brand banner
Teams often override colors to align alerts with domain-specific dashboards or tenant branding.
Muted reminder
The component still keeps the same layout, icon slotting and close mechanics while colors are fully customized.

Custom Colors
<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>

Документация компонентаВсе компоненты