Source: https://monarchui.dev/components/callout-banner/

Components · Status and feedback

# CalloutBanner

Page- or section-level banner: trials, maintenance, billing, failed syncs and narrowed scopes.

@rookcom/monarch-react

Component

## Page banner with an action

## Usage examples

### Tones

### Scope notice

## Purpose

A message about the account, the product or the whole view, shown above the content it affects. It can carry actions, be dismissed (the product decides whether that sticks) and be a landmark.

### Use when

- Account or workspace state that affects everything: a trial ending, a failed payment, a plan limit.
- Planned or ongoing maintenance, or a degraded service.
- A section-level problem with a fix, such as a failed sync with Retry.
- The view is narrowed to a Space, team or filter and the user should be able to clear it (`variant="scope"`).

### Avoid when

- Confirming an action that just happened: use Toast.
- A short note attached to one field, row or card: use InlineNotice.
- A message about one section with a title and a paragraph that never needs dismissing: Alert is enough.
- The user must decide before continuing: use Dialog.

## Design principles

- One banner per placement: stack at most one `page` banner above the app and one `section` banner above a view.
- Lead with the consequence ("Your trial ends in 3 days"), then what to do, then the action.
- Silent by default. Set `live` only when the banner appears because of something that just happened.
- Dismissal persistence is the product’s: `onDismiss` fires, Monarch stores nothing, and `defaultOpen={false}` hides a banner the user already closed.
- Tone carries meaning, never colour alone: every tone has its own icon, and the text says what happened.

## API reference

```tsx
import { CalloutBanner } from '@rookcom/monarch-react';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `CalloutBanner` | component | client | `.mn-callout-banner` |

Forwards its ref to the root element (`<div>`, `<section>` or `<aside>` depending on `landmark`).

All native attributes pass through to the root, including `aria-label` for a landmark with no title.

Renders nothing while closed.

Generated from the TypeScript declarations of `packages/react/src/components/feedback/callout-banner`.

## Properties

### `CalloutBanner`

A page- or section-level message about the account, the product or the current view: trials, maintenance windows, billing problems, a failed sync, a narrowed scope. Optionally dismissible, with an action slot and a landmark role.

| Prop | Type | Description |
| --- | --- | --- |
| `actionPlacement` | `"inline" \| "end"` | `end` puts actions on the trailing edge (below the text on narrow screens); `inline` puts them right after the text, like a sentence that ends in a link. Default `end`. |
| `actions` | `ReactNode` | Buttons or links, for example "Upgrade" or "Retry". |
| `children` | `ReactNode` |  |
| `className` | `string` |  |
| `defaultOpen` | `boolean` | Initial visibility when uncontrolled. Default true. |
| `dismissible` | `boolean` | Shows a close button. Implied by `onDismiss`. |
| `dismissLabel` | `string` | Accessible name of the close button. Default "Dismiss". |
| `icon` | `ReactNode` | Replaces the tone icon. Pass `null` for none. |
| `id` | `string` |  |
| `landmark` | `"none" \| "complementary" \| "region"` | Makes the banner a landmark, so screen-reader users can jump to it: `region` (`<section>`) or `complementary` (`<aside>`). Named by `title`, or by `aria-label` when there is no title. Default `none` (`<div>`). |
| `live` | `"polite" \| "assertive"` | Announce the banner's text when it appears: `polite` (role="status") or `assertive` (role="alert", only for errors that block progress). Leave unset for a banner that is present when the page loads. |
| `onDismiss` | `(() => void)` | Called when the user dismisses the banner (and shows the close button). Store "don't show again" here: Monarch keeps nothing. |
| `open` | `boolean` | Controlled visibility. Leave unset to let the banner hide itself on dismiss. |
| `placement` | `"section" \| "page"` | `page`: a full-width strip across the top of the app or page (trial, maintenance, billing), with no rounded corners. `section`: a rounded callout inside the content. Default `section`. |
| `title` | `ReactNode` | Bold first line. |
| `tone` | `CalloutBannerTone` | Colour and default icon. `accent` uses the product accent, for trials, upgrades and announcements. Default `info`. |
| `variant` | `"callout" \| "scope"` | `scope`: a compact, neutral one-line notice that the view is narrowed ("Showing tickets in Support"), usually with an action that clears it. Default `callout`. |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `tone` | `'neutral'``'info'``'accent'``'success'``'warning'``'danger'` | Colours and default icon. `accent` follows the product accent, for trials and announcements. Default `info`. Exposed as `data-tone`. |
| `placement` | `'section'``'page'` | `section`: a rounded callout inside the content. `page`: a full-width strip with only a bottom border, for the top of the app. Default `section`. Exposed as `data-placement`. |
| `variant` | `'callout'``'scope'` | `scope`: a compact one-line "you are looking at a subset" notice with no icon by default. Default `callout`. Exposed as `data-variant`. |
| `actionPlacement` | `'end'``'inline'` | `end` puts actions on the trailing edge (below the text on narrow screens); `inline` right after the text. Default `end`. Exposed as `data-action-placement`. |
| `landmark` | `'none'``'region'``'complementary'` | Root element and landmark role: `<div>`, `<section>` (region) or `<aside>` (complementary). Default `none`. |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Open | – | Rendered. The default. |
| Dismissed | – | Uncontrolled: hidden after the close button is pressed. Controlled: hidden when `open` becomes false. |
| Live | `role="status" \| role="alert" (on the text)` | Announced when it appears; only when `live` is set. |

## Interaction behaviour

- The close button appears when `dismissible` or `onDismiss` is set; pressing it calls `onDismiss` and, when uncontrolled, hides the banner.
- With `open`, the banner stays until the parent sets `open={false}`, for example after saving the dismissal.
- If focus was on the close button, it moves to the next focusable element after the banner (or the previous one), never to the page body.
- Actions are ordinary buttons or links passed in `actions`.

## Keyboard support

| Keys | Action |
| --- | --- |
| Tab | Moves through the actions, then the close button. |
| Enter / Space | Activates the focused action or dismisses the banner. |

The banner itself never takes focus when it appears.

## Accessibility

Role

none by default; region (

<section>

) or complementary (

<aside>

) with

landmark

Accessible name

A landmark is named by

title

(aria-labelledby) or by

aria-label

.

- The live role (`status` or `alert`) is on the text, not the root, so a landmark keeps its own role and the announcement is only the message.
- The tone icon is `aria-hidden`; the title and body carry the meaning.
- The close button is an IconButton labelled `dismissLabel` (default "Dismiss").
- Text and icon colours come from tokens with contrast guaranteed in every theme: title and body on the tinted background, icons in the tone’s strong colour.

## Responsive behaviour

- Below 40rem, trailing actions wrap under the text, aligned with it.
- The title and body wrap; nothing is truncated. Works down to 320px.
- `placement="page"` uses the layout gutter (`--mn-layout-gutter`) so it lines up with page content.

## Theme support

- Each tone uses its intent subtle, strong and border tokens; `accent` uses the product accent, so each Rook product gets its own colour.
- Light, dark and high-contrast themes apply automatically.
- No animation, so reduced motion needs nothing.

## Do and don't

### Do

- Store the dismissal in `onDismiss` and pass `defaultOpen={false}` (or `open`) next time.
- Give a page banner one clear action; link to details rather than packing in paragraphs.
- Use `landmark="region"` for banners people may want to return to, such as maintenance notices.

### Don't

- Do not stack several page banners; show the most urgent one.
- Do not use `live="assertive"` for promotions or reminders.
- Do not make a billing or security problem dismissible if it still blocks the user.

## Related components

- [Alert](https://monarchui.dev/components/alert/): Section message with title and body that is never dismissed.
- [InlineNotice](https://monarchui.dev/components/inline-notice/): One line attached to a field, row or card.
- [Toast](https://monarchui.dev/components/toast/): Transient confirmation of an action.
- [Button](https://monarchui.dev/components/button/): Actions and the close IconButton.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | [Button](https://monarchui.dev/components/button/) |

Intent `subtle`, `strong` and `border` tokens; `--mn-layout-gutter` for page placement.

## Testing coverage

- Renders title, body, decorative icon and actions with tone, placement and variant attributes; axe clean.
- Forwards its ref and native attributes.
- landmark="region" and "complementary" render named `<section>` / `<aside>` landmarks; axe clean.
- A landmark without a title is named by aria-label.
- The live role sits on the text inside a landmark (alert / status).
- Dismisses uncontrolled, calls onDismiss and moves focus to the next control, or the previous one.
- Controlled `open` keeps it until the parent closes it; `defaultOpen={false}` renders nothing.
- Custom dismiss label; scope variant with inline action and no default icon; icon replace and removal.
- Renders on the server.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/callout-banner |

Tests: `packages/react/src/components/feedback/callout-banner`

## All example code

### Page banner with an action

```tsx
import { Button, CalloutBanner } from '@rookcom/monarch-react';

<CalloutBanner
  placement="page"
  tone="accent"
  title="Your trial ends in 3 days"
  actions={
    <Button size="sm" variant="primary">
      Choose a plan
    </Button>
  }
  onDismiss={() => {}}
>
  Add a payment method to keep your workspace and its data.
</CalloutBanner>
```

### Tones

```tsx
import { Button, CalloutBanner } from '@rookcom/monarch-react';

<CalloutBanner tone="info" landmark="region" title="Scheduled maintenance">
  Sign-in is unavailable on Sunday from 02:00 to 03:00 UTC.
</CalloutBanner>
<CalloutBanner tone="warning" title="Payment method expires this month" />
<CalloutBanner
  tone="danger"
  title="Sync failed"
  live="assertive"
  actions={<Button size="sm">Retry</Button>}
>
  The registry did not respond. Your changes are saved locally.
</CalloutBanner>
<CalloutBanner tone="success" title="Domain verified" dismissible />
```

### Scope notice

```tsx
import { Button, CalloutBanner } from '@rookcom/monarch-react';

<CalloutBanner
  variant="scope"
  tone="neutral"
  live="polite"
  actionPlacement="inline"
  actions={
    <Button size="xs" variant="ghost" onClick={() => {}}>
      Show all Spaces
    </Button>
  }
>
  Showing tickets in <strong>Support</strong>.
</CalloutBanner>
```
