Notice card
An inline notice with an icon, a title, a body, one action and a dismiss button.
npx shadcn@latest add https://ui.goodtabs.co/r/notice-card.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/notice-card
Info: New components this week
Five cards and surfaces landed, including the resource cell from the library.
The info tone with a body and a ghost action under it.
All four tones
Info: Registry updated
Run the install command again to pick up the latest version.
Success: Deployed to production
Build 4f2c9a is live on ui.goodtabs.co. It took 48 seconds.
Warning: Preview deployments are paused
You have reached the free tier limit for this month. Production is not affected.
Error: Build failed
Type error in app/page.tsx on line 12. The previous deployment is still serving.
Info, success, warning and error, stacked. Only the icon changes.
Dismissible
Warning: Your API key expires in 3 days
Requests will fail once it expires. Rotate it now to avoid downtime.
Pass onDismiss to show the close button. The demo brings it back.
Usage
import { Button } from "@/components/ui/button"
import { NoticeCard } from "@/components/ui/notice-card"
<NoticeCard tone="warning" title="Your key expires in 3 days" onDismiss={() => setOpen(false)} action={<Button variant="ghost" size="sm">Rotate key</Button>}>
Requests will fail once it expires.
</NoticeCard>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "info" | "warning" | "error" | "success" | "info" | Picks the icon and the sr-only prefix. No colour changes. |
| title | string | The first line. | |
| children | ReactNode | The body, under the title. | |
| action | ReactNode | One action under the body, usually a ghost sm Button. | |
| onDismiss | () => void | Shows the dismiss button and is called when pressed. | |
| dismissLabel | string | "Dismiss" | Accessible name of the dismiss button. |
Source
notice-card/notice-card.tsx
import * as React from "react";
import { CircleAlertIcon, CircleCheckIcon, InfoIcon, TriangleAlertIcon, XIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
type Tone = "info" | "warning" | "error" | "success";
type NoticeCardProps = Omit<React.ComponentProps<"div">, "title"> & {
/** Picks the icon and the sr-only prefix. Monochrome: the icon and title carry the meaning. */
tone?: Tone;
/** The first line. */
title: string;
/** The body, under the title. */
children?: React.ReactNode;
/** One action under the body, usually a ghost sm Button. */
action?: React.ReactNode;
/** Shows a dismiss button top right and is called when it is pressed. */
onDismiss?: () => void;
/** Accessible name of the dismiss button. */
dismissLabel?: string;
};
const TONE_ICON = {
info: InfoIcon,
warning: TriangleAlertIcon,
error: CircleAlertIcon,
success: CircleCheckIcon,
} as const;
const TONE_TEXT = { info: "Info", warning: "Warning", error: "Error", success: "Success" } as const;
/**
* An inline notice: icon, title, body, an optional action and a dismiss button.
* All four tones are monochrome; the icon and the title say which one it is.
*/
export function NoticeCard({
tone = "info",
title,
children,
action,
onDismiss,
dismissLabel = "Dismiss",
className,
...props
}: NoticeCardProps) {
const Icon = TONE_ICON[tone];
return (
<div
data-slot="notice-card"
data-tone={tone}
className={cn("flex items-start gap-3 rounded-lg border bg-card p-4 text-sm", className)}
{...props}
>
<Icon aria-hidden="true" className="mt-0.5 size-4 shrink-0 text-foreground" />
<div className="flex min-w-0 flex-1 flex-col gap-1">
<p className="m-0 font-medium text-foreground">
<span className="sr-only">{TONE_TEXT[tone]}: </span>
{title}
</p>
{children && <div className="text-muted-foreground">{children}</div>}
{action && <div className="mt-1 -ml-2.5 flex">{action}</div>}
</div>
{onDismiss && (
<Button
type="button"
variant="ghost"
size="icon-sm"
aria-label={dismissLabel}
onClick={onDismiss}
className="-my-1 -mr-1.5 shrink-0 rounded-full text-muted-foreground"
>
<XIcon aria-hidden="true" />
</Button>
)}
</div>
);
}
For AI agents
Hand your agent this page, the markdown at /components/notice-card/prompt.md, or the Copy AI prompt button above.
When to use
- A message that belongs to the page or a section, not to a single action
- Setup hints, limits reached, expiring keys, a build that failed
- Anything the person should read once and can dismiss
When not to use
- Feedback for an action just taken; use a toast
- Something that blocks the page; use a dialog
- Field errors; show them under the field
Agent notes
- Server component unless onDismiss is passed; then render it from a client component, as the dismissible demo does
- Monochrome on purpose: tone swaps the icon and a sr-only prefix (Warning: ...), never a colour
- action is a slot: pass a ghost sm Button so it aligns with the title; the slot pulls it 10px left for that
- It does not manage its own visibility; keep the open state in the parent and unmount it on dismiss