ComponentsFeedback

Notice Banner

A full-width strip at the top of a page. Use it when something needs action, such as an integration that needs authorization. The message is on the left and the actions follow it.

Reauthorization flow

A sample adapter replaces Cloud’s queries and consent redirects. Choose outcomes, then use the banner with the keyboard or pointer.

Workspace page

3 integrations need authorization. Approve the updated permissions to continue.

No sample events yet.

Integration authorization states

Every message, select, and button state the Cloud reauthorization banner shows. Names are fictional and nothing connects.

Notice with actions

The internal playground notice: plain text and one small outline button. Dismissing it moves focus to the button that brings it back.

This is an example notice. No integration is being monitored.

Usage

Notice Banner usageTSX
import { Button } from "@tulipfarm/sprout/button";
import { NoticeBanner } from "@tulipfarm/sprout/notice-banner";

<NoticeBanner
  aria-label="Integration authorization"
  actions={<Button variant="outline">Update permissions</Button>}
>
  <p role="status">
    <span className="font-medium text-sprout-warning">
      Google Calendar ([email protected]) needs authorization.
    </span>{" "}
    Approve the updated permissions to continue.
  </p>
</NoticeBanner>

NoticeBanner renders a section with a 1px line under it, the surface color, and 14px muted text. childrenis the message. actions holds buttons, selects, or other controls. They wrap when there is no room. Leave out actionsto show only the message. Other props, like className, go to the section.

There is one size and no variants. Below 640px the message and actions stack, with 8px between them, 16px side padding, and full-width actions. From 640px they sit on one row, centered, with 16px between them and 24px side padding. Long words in the message wrap.

Use the default outline Button, 28px with 14px text, for the main action, next to a 32px Select when there is a choice. Usesize="sm", 28px with 12px text, for a single light action, like Dismiss. To render no banner, do not mount it.

Choosing and waiting

Select and busy actionTSX
<NoticeBanner
  aria-label="Integration authorization"
  actions={<>
    <div className="w-full sm:w-64">
      <Select value={selected.id} onValueChange={choose} disabled={busy}>
        <SelectTrigger width="full" aria-label="Integration to reauthorize">
          <SelectValue>{selected.name}</SelectValue>
        </SelectTrigger>
        <SelectContent>
          {items.map(item => (
            <SelectItem key={item.id} value={item.id}>{item.name}</SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
    <Button variant="outline" disabled={busy} focusableWhenDisabled aria-busy={busy}>
      {busy ? "Please wait..." : failed ? "Check integration status" : "Update permissions"}
    </Button>
  </>}
>
  {failed
    ? <p role="alert">Authorization could not be opened. Check integration status before trying again.</p>
    : <p role="status">…</p>}
</NoticeBanner>

When several items need action, put a Select before the button. Give its wrapper a full width on small screens and 16rem from 640px. While work runs, disable the select and button and set aria-busy. Add focusableWhenDisabled so keyboard focus stays on the button.

Accessibility

The banner is a section. Give it an aria-labelso it is a named region that screen reader users can jump to. Put role="status" on a normal message and role="alert" on an error, so changes are announced. All actions are normal controls in tab order.

If an action removes the banner, move focus to a sensible control, as the examples do. The banner fades in over 180ms and does not animate when reduced motion is requested. In forced colors mode the line uses the system text color.

Theme tokens

The background uses --sprout-surface, the line uses --sprout-line, and text uses --sprout-muted. A warning lead can use --sprout-warning. Motion uses --sprout-motion-normal and --sprout-motion-ease.