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.
Usage
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 (robin@greenhouse.example) 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
<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.