GrAlert

Package: @feugene/granularitycoreGroup: feedback

Shows an important message, warning or action status.

Machine-translated from the Russian original, not yet reviewed. Read the original

When to take it

  • the message belongs to a place on the page — a warning above a form, an explanation inside a section: it lives where its cause lives;
  • the message has to stay — a toast leaves on its own, this one is read for as long as it takes;
  • there is an action inside — the actions slot holds “Retry”, “Open settings”;
  • the message appears asynchronouslylive announces it to a screen reader instead of leaving it silent.

When to take something else

NeedTake
A short message about the result of an actionGrToaster
A server error with a status and detailsGrResponseErrorBanner
An error on a single fieldGrFormField
The screen is empty and that needs explainingGrEmptyState
An answer from the user is requiredGrConfirmDialog

Tone and variant are different axes

tone is responsible for colour and meaning (info, success, warning, danger, slate, azure), variant for weight: soft (a tinted background) or outline (a border only). Every combination makes sense.

The colours are expressed entirely in --gr-* tokens, so the message is equally correct in the light and the dark theme. For pointed adjustments there are the backgroundColor, textColor and borderColor props, or the --gr-alert-* variables (see tokens.md).

How the message is announced to a screen reader

The key role here belongs to live, and by default it is derived from the tone:

liveWhat the element getsWhen
auto (the default)alert for warning/danger, status for the restthe ordinary case
assertiverole="alert" — interrupts readingthe message demands an immediate reaction
politerole="status" — waits for a pausethe message can be finished later
offno role at allthe message has already been announced another way

The point of the default: role="alert" interrupts speech, and hanging it on every informational message turns a calm hint into an alarm.

The mode is configurable globally — an application whose alerts must not interrupt speech does not need the prop on every one of them:

<GrConfigProvider :component-defaults="{ GrAlert: { live: 'polite' } }">

componentDefaults also sets tone, variant and closable.

The icon

By default the glyph is chosen by the tone. The #icon slot replaces it, the :icon="false" prop removes it:

<GrAlert tone="success">
  <template #icon>
    <IconRocket />
  </template>
  Deploy finished.
</GrAlert>

<GrAlert tone="slate" :icon="false">
  Draft saved automatically.
</GrAlert>

The icon is decorative in both cases — it stays aria-hidden, because the meaning is carried by the text, not by the picture. A message without an icon reads more calmly and belongs where there are many alerts: a dense form, a list of settings.

Actions

“Retry” and “Details” buttons go into the #actions slot rather than into the text: there they get a place of their own below the message and do not break the sentence apart.

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

Dismissal: `close` versus `v-model:visible`

closable adds the button. What happens next is the consumer’s choice:

<!-- the message stays until the screen decides otherwise -->
<GrAlert closable @close="askConfirmation" />

<!-- the message hides itself -->
<GrAlert v-model:visible="shown" closable />

visible has no state of its own, and that is deliberate: without the prop the alert does not disappear on click, it only reports the intent with a close event. Otherwise the dismiss button would become an irreversible action for everyone who asks for confirmation on close or writes a mark on the server. close is emitted in both modes.

Playground 9

Loading…

Code
<GrAlert />

Install

npm i @feugene/granularity

Import

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

API

Props

PropTypedefaultDescription
tone"primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefinedundefined
variant"soft" | "outline" | undefinedundefined
titlestring | undefinedundefined
closableboolean | undefinedundefined
liveGrAlertLive | undefinedundefined
iconboolean | undefinedundefinedWhether to show the tone icon. An icon of your own goes into the `#icon` slot; the prop is for the case where the icon gets in the way: a narrow column, a dense list, a message inside a form.
visibleboolean | undefinedundefinedThe visibility of the message — **controlled only**: without the prop the alert is always visible, and closing stays the concern of the consumer (`@close`). With `v-model:visible` the component hides itself. The prop deliberately has no state of its own. It would turn the close button into an irreversible action for everyone who already lives on `@close` and, for instance, asks for confirmation there.
backgroundColorstring | undefinedundefined
textColorstring | undefinedundefined
borderColorstring | undefinedundefined

Slots

SlotTypeDescription
defaultanyThe text of the message.
iconanyAn icon instead of the glyph chosen by tone. Decorative: it stays `aria-hidden`.
actionsanyActions on the message: "Retry", "Details". They go under the text.

Events

EventTypeDescription
close[]
update:visible[value: boolean]

Examples 4

Actions, self-dismissal and a message without an icon

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>

Semantic tones for inline feedback

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>

Closable alert with host-level state

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>

Brand-specific colors without layout overrides

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>

Component documentationAll components