FairGarden Design

Announcement Bar

One short, authored site notice per page: a closure, a deadline, shipping terms, a policy change. An aside labelled "Announcement" with at most one link, an optional countdown and an optional dismiss button. It is not a Toast, never rotates messages and never carries form feedback.

import { AnnouncementBar } from '@fairgarden-private/design/components/AnnouncementBar'

Place it full width at the top of the page; it sets its own content container. The message is up to two lines at base and one from 768 px; it is never truncated and never sticky, so it scrolls away with the page. The bar is 36 px tall for text alone and 44 px once it holds a link or the dismiss.

Bars and fields

Bars and fields

kind="bar" (default) is a page-ground bar (white, paper or the page's pastel through preset, or the surrounding ground when it names none) that always draws its bottom rule. kind="field" sets the notice in a full-container field (forest by default, leaf, amber or brick; never pink or royal) whose edge replaces the rule. voice="mono" sets dated notices in data caps, and voice="dual" adds one serif caps phrase through AnnouncementBarAccent. The countdown's boxed digits update once a minute and never flip; they fill in after the page loads. dismissible removes the bar at once, and storageKey remembers that per viewer.

AnnouncementBarBars.tsx
'use client'

import * as React from 'react'
import {
  AnnouncementBar,
  AnnouncementBarAccent,
} from '@fairgarden-private/design/components/AnnouncementBar'
import styles from './bars.module.css'

/** A page-ground bar with its rule, link and dismiss; a mono countdown; a forest field bar; a dual-voice record. */
export function AnnouncementBarBars() {
  const [dismissals, setDismissals] = React.useState(0)

  return (
    <div className={styles.stack}>
      <AnnouncementBar
        key={dismissals}
        preset="white"
        link={{ href: 'https://example.org/hours', label: 'See Hours' }}
        dismissible
      >
        The visitor center is closed 24–26 December.
      </AnnouncementBar>
      <AnnouncementBar
        voice="mono"
        countdown={{ end: '2026-12-31T17:00:00Z', expired: 'Registration closed' }}
      >
        Registration closes in
      </AnnouncementBar>
      <AnnouncementBar
        kind="field"
        link={{ href: 'https://example.org/walks', label: 'Book a Walk' }}
      >
        Guided spring walks are open for booking.
      </AnnouncementBar>
      <AnnouncementBar kind="field" preset="amber" voice="dual" record>
        Product recall: <AnnouncementBarAccent>Trail Mix No. 4</AnnouncementBarAccent>, lots 12–19
      </AnnouncementBar>
      <p className={styles.status}>
        <button type="button" className={styles.reset} onClick={() => setDismissals((n) => n + 1)}>
          Show the dismissed bar again
        </button>
      </p>
    </div>
  )
}

In print

Hidden, unless record marks a notice that changes the page's meaning: it prints once, between two 0.75 pt rules, with its link's short URL. A countdown prints its end date ("Ends 30 Sept 2026").

API Reference

AnnouncementBar

The announcement bar. Never sticky, never rotating, never truncated with an ellipsis; it scrolls away with the page. Hidden in print unless it is a record, whose countdown prints its end date.

PropTypeDescription
countdown
AnnouncementCountdown | undefined

Boxed countdown digits; a polite live region announces at most once a minute.

dismissLabel
string | undefined

The dismiss button’s accessible name. Default “Dismiss announcement”.

dismissible
boolean | undefined

Adds the dismiss button (close, in a 44 px target). Default false.

kind
'bar' | 'field' | undefined

bar (default): a full-bleed page-ground bar with its bottom rule. field: a full-container field with its --primary12 edge [D180].

link
{ href: string; label: React.ReactNode } | undefined

The one link, after the message: underlined at rest in --role-accent (§9.3 body link); it prints its short URL.

onDismiss
(() => void) | undefined

Called after the bar is dismissed.

preset
| 'paper'
| 'white'
| 'tide'
| 'meadow'
| 'pollen'
| 'apricot'
| 'rose'
| 'heather'
| AnnouncementField
| undefined

The bar’s page ground: white, paper or the page’s pastel. Omitted, the bar sits on the surrounding page ground.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: message, digits, cells, glyph and focus ring. Never defaulted; omitted, it inherits the bar’s preset [D133].

record
boolean | undefined

A notice of record (a legal change, a recall): it prints once, between two 0.75 pt rules. Default false: the bar is hidden in print.

ruled
boolean | undefined

The bottom rule is required on every page-ground bar.

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale: the link underline and its hover ink. Never defaulted.

storageKey
string | undefined

Remembers the dismissal per viewer under this key (browser storage), so the bar stays gone on later visits. Change the key for a new notice.

voice
'standard' | 'mono' | 'dual' | null | undefined

standard (default): caps type-label, centered. mono: type-data caps for dated or technical notices (“CLOSED 24–26 DEC”). dual: the caps run plus one serif caps phrase, AnnouncementBarAccent.

children
React.ReactNode

The message: one short notice, authored; ≤ 2 lines at base, 1 from --md-n-above.

AnnouncementBarAccent

The dual voice’s one serif caps phrase, set inside the message at the run’s cap height (Rocco’s two-voice bar). Use it once, with voice="dual".

Additional types

announcementBar
AnnouncementBarAccentProps

Props for AnnouncementBarAccent: span props.

type AnnouncementBarAccentProps = React.ComponentPropsWithRef<'span'>
AnnouncementBarProps

Props for AnnouncementBar: aside props, the message, link, voice, record, countdown and dismissal, the kind with its preset, and the color axes. ruled is required with kind: bar and excluded with kind: field.

type AnnouncementBarProps = AnnouncementBarBarProps | AnnouncementBarFieldProps
AnnouncementCountdown

The countdown: boxed digits to an end time, which print as the end date.

type AnnouncementCountdown = {
  /** The end, a Date or an ISO 8601 string. */
  end: Date | string;
  /** The text that replaces the digits at zero, e.g. "Sale ended". */
  expired: React.ReactNode;
  /**
   * The printed end date line. Default "Ends 30 Sept 2026", formatted from
   * `end` in `locale` and the reader's time zone once the page has loaded.
   */
  endLabel?: string;
  /** The locale for the default end label. Default `en-GB`. */
  locale?: string;
  /** Unit labels after the day, hour and minute cells. Default `d`, `h`, `min`. */
  units?: [string, string, string];
}
AnnouncementField

The fields a full-container bar may take [D180]: never pink, never royal.

type AnnouncementField = 'forest' | 'leaf' | 'amber' | 'brick'

Specification: DESIGN-SYSTEM.md §11.4 (announcement bar; a page-ground bar or a full-container field per D180).