Skip to content

Patterns

Messages: success, information, warning and error

Every message in Skribly has one of four tones and lives in one of a few places. Pick the place by how long the message matters and how close it is to what it is about, then write it so it says what happened and what to do next.

The four tonesLink to the section The four tones

  • Success

    It worked, and the result is what the reader asked for.

    Success: Invitation sent to ada@example.com

    Read out
    Polite
    Stays
    A toast may close after 5 s or more
  • Information

    A neutral fact that helps, with nothing to fix.

    Information: Imports run at night

    Read out
    Polite
    Stays
    A toast may close after 5 s or more
  • Warning

    Something may go wrong or needs a decision soon. Nothing is blocked yet.

    Warning: Your draft is kept for 7 more days

    Read out
    Polite
    Stays
    Until closed
  • Error

    It failed or is blocked. Say what happened and how to fix it.

    Error: Could not publish. The server did not answer.

    Read out
    Assertive
    Stays
    Until closed or fixed

Each tone has a colour, an icon of its own shape and a word. The word, such as “Error”, is the toneLabel prop, read before the message so the meaning never rests on colour. Pass it every time.

Choose the right componentLink to the section Choose the right component

Message components, where they show, how long they stay, how they are read and what they are for
ComponentWhereHow longHow it is readUse it for
Inline field errorUnder the field it is aboutUntil the value is fixedWith the field, through aria-describedbyOne field that needs a change
FormErrorSummaryAt the top of the formUntil the next submitAssertive, and it takes focusA submit that failed on one or more fields
AlertIn the content, next to what it is aboutUntil closed, or while the state lastsStatic, or polite or assertive with liveA result or condition of one part of the page
BannerFull width at the top of a page or panelAs long as the state lastsA named region; live when it appears laterA state of the whole app, such as offline or read-only
ToastAt the edge of the screenSuccess and information at least 5 s; warnings and errors until closedPolite, or assertive for errorsThe result of an action just taken, when it is not visible elsewhere
FlashMessagesAt the top of the next pageUntil closedWritten into a live region after the page loadsA server result after a redirect, such as “Saved”
FormActions statusNext to the submit buttonsUntil the next submitPolite, or assertive for errorsThe save result of a short form

A quick rule: the closer the message is to its cause, the better. A field error beats a summary, a summary beats a banner, and a toast is for results the reader would otherwise not see.

Live examplesLink to the section Live examples

Alert with a live result

The live region is in the page before the result arrives, so the result is read out when it appears.

Alert
tsx
<div aria-live="polite">
  {result && <Alert tone="ok" toneLabel={t('tone.ok')} title={t('tracker.connected')} />}
</div>

Banner for a state of the whole app

Banner

Error: You are offline

Changes are kept on this device and sent when the connection returns.

Warning: Read-only mode

The plan has ended, so changes are not saved until it is renewed.

Page content

tsx
<Banner tone="danger" toneLabel={t('tone.error')} title={t('offline.title')} live="assertive">
  {t('offline.text')}
</Banner>

Toasts for the result of an action

Toast
tsx
const toast = useToast()
toast.success(t('saved'))
toast.error(t('publish.failed'), { description: t('publish.retry') })

Flash messages after a redirect

FlashMessages

Success: Profile saved

tsx
<FlashMessages messages={messagesFromServer} toneLabels={toneLabels} onDismiss={remove} dismissLabel={t('close')} />

Inline errors and the error summary

Submit the empty form. The summary takes focus and links to each field; each field shows its own error. Fix them and submit again for the success message.

Form messages
We send the invitation here.
tsx
<FormErrorSummary title={t('form.problems', { count })} errors={errors} />
<TextField id="name" label={t('name')} isInvalid={!!nameError} errorMessage={nameError} />

Writing guidelinesLink to the section Writing guidelines

  • Say what happened, then what to do. “Could not save. Check your connection and try again.”
  • Be specific. Name the thing and the value: “Enter a date after 1 March”, not “Invalid date”.
  • Use plain words and no blame. “That email address is already in use”, not “You entered an invalid email”. No codes unless support needs them, and then after the words.
  • Keep the title short. Up to about eight words. Put the detail in the description.
  • Repeat the verb of the button. After “Publish”, say “Published”, so the reader connects the two.
  • Stay calm. No “Oops”, no exclamation marks, no jokes in errors, and one apology at most when Skribly is at fault.
  • Warnings say the consequence and the way out. “Your plan ends in 3 days. Renew it to keep editing.”

Error: Could not publish the page

The server did not answer. Your changes are saved; try again in a minute.
Do. Say what failed, that the work is safe, and what to do.

Error: Oops! Something went wrong

Error 503.
Don’t. Hide the cause behind a code, or leave the reader without a next step.

Success: Invitation sent to ada@example.com

Do. Confirm the exact thing that happened.

Success: Success!

Don’t. Repeat the tone word as the message.

Timing and focusLink to the section Timing and focus

  • Errors and warnings never close on their own. Success and information toasts stay at least 5 seconds and pause while the pointer or focus is on them.
  • Move focus only for a blocking error after a submit, as FormErrorSummary does. Everything else is announced and leaves focus where it is.
  • Announce each message once. Render the live region empty first, then fill it; a region that appears with its text is often not read.
  • Put a toast’s action somewhere else too. A toast may close before a keyboard user reaches it, so Undo needs a second path, such as the item’s menu.
Announcing your own messagetsx
import { announce } from '@skribly/ui/theme'

announce(t('results', { count }))          // polite
announce(t('upload.failed'), 'assertive')  // errors only

Search this site

Arrows move through the results, Enter opens one, Esc closes. Ctrl K /