# Banner (Notchset): prompt.md (v2.1.0)

- id: `banner` · version 2.1.0 · component · free
- category: Feedback
- build: one set of files for every shadcn style
- install: `npx shadcn@latest add https://notchset.dev/r/banner.json`
- npm dependencies: none
- registry dependencies: utils, https://notchset.dev/r/notchset-foundation.json
- docs: https://notchset.dev/components/banner
- The install command carries everything this item needs (files, CSS, tokens, npm and registry dependencies). Prefer it to copying source by hand.

Something about this page to know before acting ("Close date passed 3 days ago"): the fact in bold, the detail, then at most one action that fixes it. The tone tints; the words carry the meaning. A card layout holds a record's own status..

## Build it
- Stack: React 18.3 or 19, TypeScript, Tailwind CSS v4 utility classes on the foundation's roles, a components.json (imports use its aliases).
- Packages: none beyond React.
- Files: `components/ui/notchset/banner.tsx`.
- Registry dependencies, installed with it automatically: shadcn `utils` (cn), `notchset-foundation`.
- Builds: one set of files, the same for every shadcn style.
- Exports to keep: `Banner`, and every exported type.
- CSS: the install adds the notchset foundation (its roles, keyframes and motion switch, reading your shadcn tokens for colour) to your global stylesheet once. Nothing to import by hand.
- Re-running `add` (or `--overwrite`) re-applies those rules: put overrides in your own CSS, never in the installed rules.
- Colours: every part reads the foundation's roles (`--ns-canvas`, `--ns-ink`, `--ns-line`, `--ns-signal` …), which read your shadcn tokens (`--background`, `--foreground`, `--border`, `--destructive` …), plus the optional `--success` and `--warning`. Retheme on `:root` or any container; never add Tailwind colour classes inside the component.
- This item reads `--ns-bad`, `--ns-bad-ink`, `--ns-bezel`, `--ns-hover`, `--ns-ink`, `--ns-ink-2`, `--ns-ink-3`, `--ns-line`, `--ns-ok`, `--ns-panel`, `--ns-radius`, `--ns-radius-lg`, `--ns-raised`, `--ns-signal`, `--ns-text-body`, `--ns-text-small`, `--ns-time-color`, `--ns-warn`, `--ns-weight-medium`, `--ns-well`.
- Retune it (on any ancestor) with the foundation's roles and hooks: `--ns-bad`, `--ns-bad-ink`, `--ns-band`, `--ns-band-edge`, `--ns-band-grade`, `--ns-band-optimal`, `--ns-beat`, `--ns-bezel`, `--ns-canvas`, `--ns-cat-1`, `--ns-cat-2`, `--ns-cat-3`, `--ns-cat-4`, `--ns-cat-5`, `--ns-cat-6`, `--ns-check`, `--ns-chip`, `--ns-control`, `--ns-control-pad`, `--ns-control-sm`, `--ns-deep`, `--ns-deep-ink`, `--ns-deep-ink-2`, `--ns-deep-line`, `--ns-dialog`, `--ns-dialog-lg`, `--ns-dialog-sm`, `--ns-ease`, `--ns-ease-in`, `--ns-ease-panel`, `--ns-ease-pop`, `--ns-field`, `--ns-flood-arc`, `--ns-flood-aurora`, `--ns-flood-behind-1`, `--ns-flood-behind-2`, `--ns-flood-bloom`, `--ns-flood-deep`, `--ns-flood-dial-bloom`, `--ns-flood-glass`, `--ns-flood-grain`, `--ns-flood-image`, `--ns-flood-ink`, `--ns-flood-ink-low`, `--ns-flood-light`, `--ns-flood-mid`, `--ns-flood-pale`, `--ns-flood-pane`, `--ns-flood-pane-edge`, `--ns-flood-pill-ink`, `--ns-flood-ribbon`, `--ns-flood-ribbon-core`, `--ns-flood-shadow`, `--ns-flood-sheen`, `--ns-flood-wash`, `--ns-font-body`, `--ns-font-display`, `--ns-font-mono`, `--ns-font-script`, `--ns-glass`, `--ns-glass-blur`, `--ns-glass-shadow`, `--ns-glow`, `--ns-grain`, `--ns-gutter`, `--ns-header`, `--ns-heat`, `--ns-hover`, `--ns-ink`, `--ns-ink-2`, `--ns-ink-3`, `--ns-ink-4`, `--ns-leading`, `--ns-lift`, `--ns-light-1`, `--ns-light-2`, `--ns-light-3`, `--ns-light-spread`, `--ns-light-strength`, `--ns-line`, `--ns-line-soft`, `--ns-lit-edge`, `--ns-menu-dy`, `--ns-menu-row`, `--ns-mesh`, `--ns-mesh-frost`, `--ns-ok`, `--ns-ok-ink`, `--ns-orb-body`, `--ns-orb-cool`, `--ns-orb-core`, `--ns-orb-dawn`, `--ns-orb-deep`, `--ns-orb-edge`, `--ns-orb-grain`, `--ns-orb-ground`, `--ns-orb-halo`, `--ns-orb-inner`, `--ns-orb-key`, `--ns-orb-lift`, `--ns-orb-limb`, `--ns-orb-limb-from`, `--ns-orb-rim`, `--ns-orb-skin-hi`, `--ns-orb-skin-lo`, `--ns-orb-skin-mid`, `--ns-orb-warm`, `--ns-panel`, `--ns-panel-w`, `--ns-prism-edge`, `--ns-prism-glint`, `--ns-prism-sheen`, `--ns-prism-tint`, `--ns-pulse`, `--ns-radius`, `--ns-radius-card`, `--ns-radius-full`, `--ns-radius-lg`, `--ns-radius-sm`, `--ns-radius-xl`, `--ns-raised`, `--ns-range`, `--ns-row`, `--ns-row-compact`, `--ns-segment`, `--ns-selected`, `--ns-series`, `--ns-series-core`, `--ns-series-lilac`, `--ns-series-tint`, `--ns-shadow-dialog`, `--ns-shadow-float`, `--ns-shadow-panel`, `--ns-sheet`, `--ns-sheet-bottom`, `--ns-side`, `--ns-signal`, `--ns-sleep-awake`, `--ns-sleep-deep`, `--ns-sleep-light`, `--ns-sleep-rem`, `--ns-space-1`, `--ns-space-2`, `--ns-space-3`, `--ns-space-4`, `--ns-space-5`, `--ns-space-6`, `--ns-space-8`, `--ns-space-section`, `--ns-stack-on`, `--ns-standing-critical`, `--ns-standing-in`, `--ns-standing-out`, `--ns-state-doing`, `--ns-state-done`, `--ns-state-review`, `--ns-state-todo`, `--ns-switch-h`, `--ns-switch-w`, `--ns-text-body`, `--ns-text-control`, `--ns-text-display`, `--ns-text-figure`, `--ns-text-headline`, `--ns-text-hero`, `--ns-text-lede`, `--ns-text-meta`, `--ns-text-page`, `--ns-text-record`, `--ns-text-small`, `--ns-text-strong`, `--ns-text-sub`, `--ns-text-title`, `--ns-tile-shadow`, `--ns-time-born`, `--ns-time-color`, `--ns-time-dialog`, `--ns-time-menu`, `--ns-time-page`, `--ns-time-panel`, `--ns-time-toast`, `--ns-tint`, `--ns-tint-ink`, `--ns-toast-bottom`, `--ns-toast-left`, `--ns-touch`, `--ns-tracking-display`, `--ns-tracking-headline`, `--ns-warn`, `--ns-warn-ink`, `--ns-wash-clay`, `--ns-wash-dusk`, `--ns-wash-graphite`, `--ns-wash-ink`, `--ns-wash-sage`, `--ns-wash-sand`, `--ns-weight-light`, `--ns-weight-medium`, `--ns-weight-regular`, `--ns-weight-strong`, `--ns-well`.
- Never add Tailwind colour classes inside the component.

```tsx
import { Banner } from "@/components/ui/notchset/banner";
```

## Parts
- A single export that takes props.

## Match the original
- Read `components/ui/notchset/banner.tsx` as the reference implementation before changing or recreating anything, and match it: sizes, colours per theme, motion timings, copy and behaviour.
- If you deviate (a prop you can't honour, a style you changed, a dependency you swapped), say so in your reply, part by part.
- Keep the accessibility contract, the keyboard map and the motion levels listed below.

## Use it when

### Not when

## Mistakes
- Recreating the component instead of installing it

## Usage

```tsx
"use client";
import { CalendarX, SignalLow, Sparkles, Timer } from "lucide-react";
import { Banner } from "@/components/ui/notchset/banner";

export default function BannerDemo() {
  return (
    <div className="flex w-full max-w-[560px] flex-col gap-2">
      <Banner icon={<Timer />} title="Closes in 6 days">Send the order form to Priya</Banner>
      <Banner tone="warn" icon={<CalendarX />} title="Close date passed" action={{ label: "Update", onClick: () => {} }}>
        3 days ago
      </Banner>
      <Banner tone="warn" icon={<SignalLow />} title="Quiet for 18 days" action={{ label: "Log a touch", onClick: () => {} }}>
        last: Demo with the security team
      </Banner>
      <Banner tone="ok" icon={<Sparkles />} title="Airtable is set up." action={{ label: "Email Ana", onClick: () => {} }}>
        Start the conversation with Ana.
      </Banner>
    </div>
  );
}
```

This example also uses the npm package `lucide-react`. Installing this item doesn't bring them; to paste the example as it is: `npx shadcn@latest add https://notchset.dev/r/banner.json`.

## Props

| Prop | Type | Default | What it does |
|---|---|---|---|
| `tone` | `keyof typeof tones` | `"neutral"` |  |
| `icon` | `React.ReactNode` |  |  |
| `title` | `React.ReactNode` |  | Required. |
| `action` | `{ label: string; onClick: () => void }` |  | The one action that fixes what the banner says. |
| `actions` | `React.ReactNode` |  | When it takes a decision rather than one fix (Deny · Approve): the system's own buttons, at the end. |
| `layout` | `"bar" \| "card"` | `"bar"` |  |

Full docs: https://notchset.dev/components/banner

## Customising
- Phones: below 720px it switches to its phone layout in CSS. Force one with `layout="desktop"` or `layout="mobile"`.
- Colours: every part reads the foundation's roles (`--ns-canvas`, `--ns-ink`, `--ns-line`, `--ns-signal` …), which read your shadcn tokens (`--background`, `--foreground`, `--border`, `--destructive` …), plus the optional `--success` and `--warning`. Retheme on `:root` or any container; never add Tailwind colour classes inside the component.
- Dark mode follows the `.dark` class or `data-theme="dark"` on an ancestor (the shadcn and next-themes conventions).
- Update later: Commit or stash local changes first (or copy the files), so the update can be undone. Read https://notchset.dev/r/changelog.json for the new versions: a major version, an entry marked breaking or a merge says what to change. Then re-run the install with `--overwrite`, on a branch. It replaces the component's files, the shared helper files they import, and the item's CSS rules in the global stylesheet, so review the whole diff (components, shared files, CSS), re-apply your own edits, and run the app's typecheck, tests and build.

## Accessibility contract (preserve when editing)

## Install

```bash
npx shadcn@latest add https://notchset.dev/r/banner.json
```

Or add the `@notchset` registry to components.json and run `npx shadcn@latest add @notchset/banner`. Every components.json style gets the same files.

## Continue the workflow

- [Empty state](https://notchset.dev/components/empty-state)
- [Operation](https://notchset.dev/components/operation)
- [Priority mark](https://notchset.dev/components/priority-mark)

## Source

### components/ui/notchset/banner.tsx

```tsx
// Notchset banner 2.1.0 · https://notchset.dev/changelog
import * as React from "react";
import { cn } from "@/lib/utils";

/*
 * Banner: something about this page the reader should know before acting ("Close date passed 3 days ago").
 * Bold fact first, then the detail, then at most one action that fixes it. Tone tints the well and the icon;
 * the words carry the meaning. Not a live region: a banner is there when the page arrives, not announced.
 * The card layout is for a record's own status (a quote waiting on Finance): a raised card, the fact on one line
 * and the detail under it, its buttons on their own row on a phone so the words keep their width.
 */
const cardTones = {
  neutral: "bg-[var(--ns-panel)] shadow-[inset_0_0_0_1px_var(--ns-line)] [&>svg]:text-[var(--ns-ink-3)]",
  warn: "bg-[var(--ns-panel)] shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--ns-warn)_45%,var(--ns-line))] [&>svg]:text-[var(--ns-warn)]",
  ok: "bg-[var(--ns-panel)] shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--ns-ok)_45%,var(--ns-line))] [&>svg]:text-[var(--ns-ok)]",
  bad: "bg-[var(--ns-panel)] shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--ns-bad)_45%,var(--ns-line))] [&>svg]:text-[var(--ns-bad-ink)]",
} as const;
const tones = {
  neutral: "bg-[var(--ns-well)] shadow-[inset_0_0_0_1px_var(--ns-line)]",
  warn: "bg-[color-mix(in_oklab,var(--ns-warn)_8%,var(--ns-well))] shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--ns-warn)_25%,transparent)] [&>svg]:text-[var(--ns-warn)]",
  ok: "bg-[color-mix(in_oklab,var(--ns-ok)_8%,var(--ns-well))] shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--ns-ok)_25%,transparent)] [&>svg]:text-[var(--ns-ok)]",
  bad: "bg-[color-mix(in_oklab,var(--ns-bad)_7%,var(--ns-well))] shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--ns-bad)_25%,transparent)] [&>svg]:text-[var(--ns-bad-ink)]",
} as const;

type BannerProps = Omit<React.ComponentPropsWithoutRef<"div">, "title"> & {
  tone?: keyof typeof tones | undefined;
  icon?: React.ReactNode;
  title: React.ReactNode;
  /** The one action that fixes what the banner says. */
  action?: { label: string; onClick: () => void } | undefined;
  /** When it takes a decision rather than one fix (Deny · Approve): the system's own buttons, at the end. */
  actions?: React.ReactNode;
  layout?: "bar" | "card" | undefined;
};

function Banner({ tone = "neutral", icon, title, action, actions, layout = "bar", className, children, ...props }: BannerProps) {
  if (layout === "card")
    return (
      <div
        data-slot="banner"
        data-tone={tone}
        data-layout="card"
        className={cn("flex flex-wrap items-start gap-x-3 gap-y-2.5 rounded-[var(--ns-radius-lg)] px-4 py-3 text-[length:var(--ns-text-body)] text-[var(--ns-ink-2)] [&>svg]:mt-[3px] [&>svg]:size-3.5 [&>svg]:shrink-0", cardTones[tone], className)}
        {...props}
      >
        {icon}
        <span className="flex min-w-0 flex-1 flex-col gap-0.5">
          <b className="font-[number:var(--ns-weight-medium)] text-[var(--ns-ink)]">{title}</b>
          {children ? <span className="text-[length:var(--ns-text-small)]">{children}</span> : null}
        </span>
        {(actions || action) && (
          <span className="flex shrink-0 items-center gap-2 self-center max-[600px]:basis-full max-[600px]:pl-[26px]">
            {actions ?? <button type="button" onClick={action!.onClick} className="h-[30px] cursor-pointer rounded-[var(--ns-radius)] bg-[var(--ns-raised)] px-3 text-[length:var(--ns-text-small)] font-[number:var(--ns-weight-medium)] text-[var(--ns-ink)] shadow-[var(--ns-bezel)] outline-hidden hover:bg-[var(--ns-hover)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ns-signal)] pointer-coarse:h-9">{action!.label}</button>}
          </span>
        )}
      </div>
    );
  return (
    <div
      data-slot="banner"
      data-tone={tone}
      className={cn(
        "flex flex-wrap items-center gap-2.5 rounded-[10px] py-[9px] pr-2.5 pl-3 text-[length:var(--ns-text-body)] text-[var(--ns-ink-2)] [&>svg]:size-3.5 [&>svg]:shrink-0",
        tones[tone],
        className,
      )}
      {...props}
    >
      {icon}
      <span className="min-w-0 flex-1">
        <b className="font-[number:var(--ns-weight-medium)] text-[var(--ns-ink)]">{title}</b>
        {children ? <> · {children}</> : null}
      </span>
      {action && (
        <button
          type="button"
          onClick={action.onClick}
          className="h-[26px] shrink-0 cursor-pointer rounded-[7px] bg-[var(--ns-raised)] px-2.5 text-[length:var(--ns-text-small)] font-[number:var(--ns-weight-medium)] text-[var(--ns-ink)] shadow-[var(--ns-bezel)] outline-hidden [transition:background-color_var(--ns-time-color)] hover:bg-[var(--ns-hover)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ns-signal)] max-[960px]:h-9 pointer-coarse:h-9"
        >
          {action.label}
        </button>
      )}
      {actions && <span className="flex shrink-0 items-center gap-2">{actions}</span>}
    </div>
  );
}

export { Banner, type BannerProps };
```

The foundation (the tokens listed above, keyframes and motion levels) installs once with the first component; its CSS is public at https://notchset.dev/r/notchset-foundation.json.
