Skip to content
BigBang

Popular

    ↑↓Navigate↵OpenEscClose
    TR

    Patterns

    Notifications

    Which channel to use for feedback, severity levels and live regions.

    A product tells users something every day: a field is wrong, a record is complete, the system is in maintenance. This pattern decides the channel, the severity and the live region for each message.

    • When you report the result of an action, a warning or an error to the user.
    • When you decide where a message goes: under a field, in a section, on the page, as a short confirmation or in the notification centre.
    • When you choose how a message is announced to a screen reader.

    Try the same message in three channels: InlineAlert for a section, Banner for the whole page, a toast for a short confirmation. The success toast offers “Undo”. Messages that appear later are announced: errors assertively, the rest politely.

    Live example, StorybookOpen in Storybook
    Try the same message in three channels: inline alert, page banner and toast. Listen with a screen reader on.
    Inline alert (section)
    Banner (page-wide)
    Toast (transient)
    import { Banner, Button, InlineAlert, Inline, Stack, Text, useToast } from '@bigbang/ui';
    import { useState } from 'react';
    import { type Lang, LangProvider } from '../lang';
    const copy = {
    tr: {
    intro: 'Aynı iletiyi üç kanaldan deneyin: satır içi uyarı, sayfa bandı ve toast. Ekran okuyucu açıkken dinleyin.',
    inline: 'Satır içi uyarı (bölüm)',
    banner: 'Banner (sayfa geneli)',
    toast: 'Toast (geçici onay)',
    tones: { success: 'Başarı', warning: 'Uyarı', danger: 'Hata' },
    kinds: {
    success: { title: 'Ayarlar kaydedildi', text: 'Ayarlar kaydedildi.' },
    warning: { title: 'Depolama dolmak üzere', text: 'Depolama alanının %80 kadarı dolu.' },
    danger: { title: 'Rapor gönderilemedi', text: 'Rapor gönderilemedi. Bağlantınızı denetleyip tekrar deneyin.' },
    },
    undo: 'Geri al',
    undone: 'Geri alındı',
    },
    en: {
    intro: 'Try the same message in three channels: inline alert, page banner and toast. Listen with a screen reader on.',
    inline: 'Inline alert (section)',
    banner: 'Banner (page-wide)',
    toast: 'Toast (transient)',
    tones: { success: 'Success', warning: 'Warning', danger: 'Error' },
    kinds: {
    success: { title: 'Settings saved', text: 'Settings saved.' },
    warning: { title: 'Storage almost full', text: 'About 80% of the storage is used.' },
    danger: { title: 'Report not sent', text: 'The report was not sent. Check your connection and try again.' },
    },
    undo: 'Undo',
    undone: 'Undone',
    },
    };
    type Tone = 'success' | 'warning' | 'danger';
    const tones: Tone[] = ['success', 'warning', 'danger'];
    function ToneButtons({ label, lang, onPick }: { label: string; lang: Lang; onPick: (tone: Tone) => void }) {
    const t = copy[lang];
    return (
    <Stack gap="xs">
    <Text size="sm" weight="medium">
    {label}
    </Text>
    <Inline gap="sm" wrap>
    {tones.map((tone) => (
    <Button key={tone} variant="secondary" size="sm" tone={tone === 'danger' ? 'danger' : 'neutral'} onClick={() => onPick(tone)}>
    {t.tones[tone]}
    </Button>
    ))}
    </Inline>
    </Stack>
    );
    }
    function Lab({ lang }: { lang: Lang }) {
    const t = copy[lang];
    const toast = useToast();
    // A new `key` per pick remounts the message, so it is announced again and its own dismissal resets.
    const [inline, setInline] = useState<{ tone: Tone; key: number } | null>(null);
    const [banner, setBanner] = useState<{ tone: Tone; key: number } | null>(null);
    const showToast = (tone: Tone) => {
    const { title, text } = t.kinds[tone];
    // Only the success toast has an action: a short confirmation of the user's own action may offer undo.
    if (tone === 'success') {
    toast.show({ title, tone, action: { label: t.undo, onClick: () => toast.show({ title: t.undone, tone: 'neutral' }) } });
    } else {
    toast.show({ title, description: text, tone });
    }
    };
    return (
    <Stack gap="lg" className="w-full max-w-xl">
    <Text size="sm" tone="subtle">
    {t.intro}
    </Text>
    <ToneButtons label={t.inline} lang={lang} onPick={(tone) => setInline((prev) => ({ tone, key: (prev?.key ?? 0) + 1 }))} />
    <ToneButtons label={t.banner} lang={lang} onPick={(tone) => setBanner((prev) => ({ tone, key: (prev?.key ?? 0) + 1 }))} />
    <ToneButtons label={t.toast} lang={lang} onPick={showToast} />
    <Stack gap="sm" className="min-h-12">
    {banner ? (
    <Banner
    key={`banner-${banner.key}`}
    tone={banner.tone}
    announce
    dismissible
    onDismiss={() => setBanner(null)}
    >
    {t.kinds[banner.tone].text}
    </Banner>
    ) : null}
    {inline ? (
    <InlineAlert key={`inline-${inline.key}`} tone={inline.tone} title={t.kinds[inline.tone].title} announce dismissible onDismiss={() => setInline(null)}>
    {t.kinds[inline.tone].text}
    </InlineAlert>
    ) : null}
    </Stack>
    </Stack>
    );
    }
    /**
    * A feedback lab for the decision matrix: a section message is an `InlineAlert`, a page-wide one a `Banner`,
    * a short confirmation of the user's own action a toast (the success toast offers "Undo").
    * Messages that appear later are announced: errors assertively, the rest politely.
    */
    export function FeedbackLab({ lang = 'tr' }: { lang?: Lang }) {
    return (
    <LangProvider lang={lang}>
    <Lab lang={lang} />
    </LangProvider>
    );
    }

    The decision matrix maps a message to a channel by its scope.

    Channel Scope and rule
    Field error Under the field; validation of that field only.
    Inline alert (InlineAlert) In a section; information, warning or error about that section.
    Banner (Banner) Page or system wide; persistent, stays until the user dismisses it or the condition ends.
    Toast (useToast) Short confirmation of the user’s own action; optional undo; auto-dismiss at 5000 ms or more, paused on hover and focus. Never for errors that need action.
    Notification centre Asynchronous events: jobs that finish while the user is elsewhere, invitations, reminders.
    Severity Icon Text
    Info info “Info”
    Success circle-check “Success”
    Warning triangle-alert “Warning”
    Danger circle-x “Error”

    Every level pairs an icon with text; colour never carries the meaning alone.

    Live region Use
    Polite (role="status") Status messages: success, warning, progress.
    Assertive (role="alert") Only errors that block.
    Enter the email address as name@domain.com.
    DoA field error sits under the field, with text.
    Email address
    Don'tA message given only by colour does not reach some users.
    Success: Settings saved.
    DoIcon and word together: colour does not carry it alone.
    Settings saved.
    Don'tColoured text without an icon or a word carries only the colour.
    1. Decide the scope of the message: one field, a section, the page, a short confirmation or an asynchronous event.
    2. Pick the channel from the table; never give the same message through two channels.
    3. Pick the severity and add the icon and the word.
    4. If the message appears after the page has loaded, turn on announce: the component announces an error assertively (role="alert") and the other severities politely (role="status"). A toast is announced in its own region.
    5. A dismissible message has a dismiss button; dismissing fades the message out and moves focus to the next focusable element.
    6. A toast closes by itself and never holds critical information alone; the result is visible somewhere else too.

    To guide users after an error, see the Error recovery pattern.

    • Status messages are written into a role="status" region; screen readers read them without cutting off the current task.
    • role="alert" is for blocking errors only; making every warning assertive tires the reader.
    • InlineAlert and Banner speak the severity with a hidden prefix (“Error:”, “Warning:”); a toast is announced in the notification region (“Notifications”). An error toast stays until it is closed.
    • The dismiss button has a name (“Dismiss”) and focus moves to the next focusable element.
    • A toast pauses on hover and focus and lasts at least 5000 ms (WCAG 2.2.1).
    • Colour never carries the meaning alone; an icon and a word are always present (WCAG 1.4.1).
    • A message says what happened; an error also says how to fix it.
    • The component speaks the severity word (“Success:”); do not repeat it in the text: “Settings saved.”
    • A toast is one sentence and holds no action except undo.
    • Avoid blame and vague wording (“An error occurred”).
    Item Motion Preset
    Toast In 240 ms, out 150 ms, 8 px shift motion.toast
    Field error text 150 ms fade, 4 px drop motion.fieldMessage
    Inline message and Banner Fade on dismiss motion.collapsible

    With reduced motion the shifts are removed and only a short fade stays.

    InlineAlert, Banner, Toast, Button, Icon, Text, TextInput. Related patterns: Forms and validation, Error recovery.