Skip to content
txbrendan/ui

Notice

beta

One quiet line under the thing a problem or note is about, with a tone-colored icon and no box.

Preview

tone

Note. Only admins can see this list.
New. Replies now show who read them.
Declined. The card on file has expired.
Paid. The receipt went to the email on file.
Due at 14:40. Later bookings in Room 3 move with it.

size

Not saved. Check your connection and try again.
Not saved. Check your connection and try again.

in place

Add the part after the @, like sam@example.com.

October invoices

6 have not paid. Reminders go out on Friday. See who

icon

Replies close on Friday at 18:00.
Only admins can see this.
Agreed. Long reasons wrap under the text, so the icon stays on the first line.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/notice

Use it for

  • Saying a problem in one line, right under the field, row, or card it is about.
  • Put the few words that say what happened in NoticeStatus, like 'Declined'. The reason after them stays in the text color.
  • Use tone to say what kind of note it is: danger for a problem, warning for a risk, success for done, neutral or brand for a plain note.
  • Use size='sm' under a field, and the default 'md' under a card or a row.
  • Add role='alert' only for an urgent problem that appears after the page loads, like a failed save.

Avoid it for

  • A message about a whole page or section, like planned downtime. Use Alert.
  • A choice people must make before they go on. Use a dialog.
  • A tinted box or a banner around the line. The point of a notice is that it stays quiet.
  • role='alert' on a notice that is there when the page loads. Screen readers would interrupt people for news that is not new.
  • More than one notice under the same thing. Say the most important problem, and its fix.

Props

  • tone

    What kind of note it is. Colors the icon and NoticeStatus.

    "neutral" | "brand" | "danger" | "success" | "warning" · default "neutral"

  • size

    Text and icon size. sm reads at the meta size for a note under a field.

    "sm" | "md" · default "md"

  • icon

    Replaces the tone's icon. Pass null for no icon. Hidden from screen readers either way.

    ReactNode | null

  • role

    Unset by default. Set it only for notices that appear or change after load. TypeScript rejects other roles.

    "alert" | "status"

  • className

    Extra classes for the line. Later classes win.

    string

  • NoticeStatus

    Part. The few words that say what happened. They take the tone's text color.

    ComponentProps<"span">

Accessibility

  • Renders a <div> with no role, so a notice that is there at load is read in order, like the rest of the page.
  • role="alert" reads the notice at once. role="status" waits until the screen reader is quiet. Use either only for notices added or changed after load.
  • The icon is aria-hidden, and color is not read out. The words must say what kind of note it is, like "Declined" or "Not saved".
  • The icon and NoticeStatus use the tone's soft foreground. The theme tests hold it to 4.5:1 on the page, on cards, and in popovers.
  • Under a field, point the field's aria-describedby at the notice's id, so the reason is read with the field.

Examples

A problem under a row

<div className="grid gap-1">
  <p className="text-sm font-medium">October invoices</p>
  <Notice tone="danger">
    <NoticeStatus>Declined.</NoticeStatus> The card on file has expired.
  </Notice>
</div>

Under a field, read with it

<div className="grid gap-1.5">
  <label htmlFor="team-email">Team email</label>
  <input id="team-email" aria-describedby="team-email-note" aria-invalid defaultValue="sam@" />
  <Notice id="team-email-note" tone="danger" size="sm">
    Add the part after the @, like sam@example.com.
  </Notice>
</div>

A consequence, with a link to the fix

<Notice tone="warning">
  <NoticeStatus>Due at 14:40.</NoticeStatus> Later bookings in Room 3 move with it.{" "}
  <a href="/schedule">Open the schedule</a>
</Notice>

A save that failed after load

function SaveNote({ error }: { error: string | null }) {
  if (!error) return null;
  return (
    <Notice role="alert" tone="danger">
      <NoticeStatus>Not saved.</NoticeStatus> {error}
    </Notice>
  );
}

Your own icon, or none

import { Clock } from "lucide-react";

<div className="grid gap-2">
  <Notice icon={<Clock />}>Replies close on Friday at 18:00.</Notice>
  <Notice icon={null} size="sm">
    Only admins can see this.
  </Notice>
</div>