Theme studio

Design a theme, preview it live, then export it. Saved in this browser.

Quick picks #10b981
Generated scale
50100200300400500600700800900
Some text is below AA

The right place for a message

Put each message as close as possible to what it's about, and make it last as long as the problem does.

Enter a full email address, like sara@northwind.io

The problem

Every message ends up in a toast because it's easy to call. But a toast saying “Email is invalid” disappears before people find the field, and a page-wide banner about one card makes everyone read it. Messages in the wrong place are either missed or ignored.

When to use it

Use it for

  • Validation errors, warnings, confirmations, outages and announcements.
  • Any time a team is about to add another toast or banner.

Not for

  • Questions that need an answer before work continues: use a Dialog.
  • Loading: use a Skeleton or the loading state of the button.

Anatomy

  1. FieldHelper text and errorMessage under an Input. About one field.
  2. SectionAn Alert inside a card, a form or a table. About that section.
  3. Page or appA Banner across the top. About the whole product: an outage, a trial ending.
  4. MomentA Toast. Confirms an action that just happened, then goes away. Never the only record of an error.
  5. AbsenceAn EmptyState where the content would be. Explains why there's nothing.

How to build it

  1. Ask what the message is about: a field, a section, the whole page, or an action that just happened.
  2. Put it there: errorMessage on the field, an Alert in the section, a Banner on the page, a Toast for the action.
  3. Match how long it stays to how long the problem lasts. Errors stay until fixed; confirmations can go.
  4. Use one tone per message: info, success, warning or danger. Danger only when something failed or will be lost.
  5. Give every persistent message a way forward: a fix, a retry or a link.
  6. Limit Banners to one at a time, and let people dismiss the ones that are only news.
app.tsx
import { Alert, Banner, Button, Input, useToast } from "@nexera-ui/react";

export function LeaveRequest({ exportDelayed, balances, onRetry }: Props) {
  const toasts = useToast();
  return (
    <>
      {/* About the whole app, until it's fixed */}
      {exportDelayed && <Banner tone="warning" message="Payroll export is delayed. We're on it." />}

      {/* About one section */}
      {balances.error && (
        <Alert
          tone="danger"
          title="Couldn't load leave balances"
          description="Your requests are saved."
          actions={<Button size="sm" variant="secondary" onPress={onRetry}>Try again</Button>}
        />
      )}

      {/* About one field */}
      <Input label="Work email" isInvalid errorMessage="Enter a full email address" />

      {/* About an action that just happened */}
      <Button
        onPress={() => {
          toasts.add({ title: "Request sent to Omar", tone: "success" });
        }}
      >
        Send request
      </Button>
    </>
  );
}

Accessibility

  • Alert, Banner and Toast are live regions: they're read out when they appear, without moving focus.
  • Danger messages are assertive and interrupt; everything else is polite. Don't make news assertive.
  • Field errors are linked to the field with aria-describedby, so they're read when it's focused.
  • Toasts pause while hovered or focused, and stay at least 5 seconds; never put the only copy of an error in one.

Do and don't

DoShow a field error under the field, until it's fixed.

Don'tShow “Email is invalid” in a toast that disappears.

DoUse a Banner only for things that affect the whole product.

Don'tPut a banner on the page for a problem in one card.

DoConfirm finished actions with a short Toast.

Don'tInterrupt with a “Success!” dialog that needs closing.