Skip to content
goodtabs UI
BrowseBrowse components

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

PropTypeDefaultDescription
tone"info" | "warning" | "error" | "success""info"Picks the icon and the sr-only prefix. No colour changes.
titlestringThe first line.
childrenReactNodeThe body, under the title.
actionReactNodeOne action under the body, usually a ghost sm Button.
onDismiss() => voidShows the dismiss button and is called when pressed.
dismissLabelstring"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