GrAlert
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 asynchronously —
liveannounces it to a screen reader instead of leaving it silent.
When to take something else
| Need | Take |
|---|---|
| A short message about the result of an action | GrToaster |
| A server error with a status and details | GrResponseErrorBanner |
| An error on a single field | GrFormField |
| The screen is empty and that needs explaining | GrEmptyState |
| An answer from the user is required | GrConfirmDialog |
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:
live | What the element gets | When |
|---|---|---|
auto (the default) | alert for warning/danger, status for the rest | the ordinary case |
assertive | role="alert" — interrupts reading | the message demands an immediate reaction |
polite | role="status" — waits for a pause | the message can be finished later |
off | no role at all | the 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…
<GrAlert />Install
npm i @feugene/granularityImport
import { GrAlert } from '@feugene/granularity/components/GrAlert'API
Props
| Prop | Type | default | Description |
|---|---|---|---|
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 | Whether 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. |
visible | boolean | undefined | undefined | The 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. |
backgroundColor | string | undefined | undefined | — |
textColor | string | undefined | undefined | — |
borderColor | string | undefined | undefined | — |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | The text of the message. |
icon | any | An icon instead of the glyph chosen by tone. Decorative: it stays `aria-hidden`. |
actions | any | Actions on the message: "Retry", "Details". They go under the text. |
Events
| Event | Type | Description |
|---|---|---|
close | [] | — |
update:visible | [value: boolean] | — |
Examples 4
Actions, self-dismissal and a message without an icon
<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
<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
<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
<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>