# Announcement Dialog

> A launch popup with feature tiles and one button, plus an optional review step that takes people from the news to checkout.

- Name: `announcement-dialog`
- Status: beta
- Categories: announcement, launch, modal, checkout, marketing
- Files: the install writes `announcement-dialog.tsx`, and any part files named `announcement-dialog-*.tsx`, to your `components` alias, `@/components` by default. Edit them freely. They are yours.
- Other registry items: `@arbokui/agreement`, `@arbokui/avatar`, `@arbokui/button`, `@arbokui/dialog`, `@arbokui/switch`. The install adds them.
- Gallery: https://arbokui.com/website/announcement-dialog
- Registry item: https://arbokui.com/r/announcement-dialog.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the website component. It also adds every component it uses:

   ```bash
   npx shadcn@latest add @arbokui/announcement-dialog
   ```

## Use it for

- Recommended: three features, each with a \`visual\` drawn from parts and a short \`title\`. Three fills the row on wide screens and stacks into rows on phones.
- Recommended: pass a \`trigger\`, like a What's new button, so people can open it again after they close it.
- Recommended for a launch: add \`showOnce\` with a key named for this launch, like "plus-launch". It opens once per browser and not again after it closes. Use a new key for the next launch.
- Recommended when the news leads to a purchase: pass \`review={\<AnnouncementReview />}\`. The main button moves to it, and its Back button comes back.
- Recommended: leave \`animated\` on. Tiles lift on hover, and reduced motion keeps them still.
- News with no checkout: give \`action\` an \`href\`, and the button links on. Pass \`className="max-w-lg"\` for one feature.
- AnnouncementReview on its own page: pass \`onBack\` to show Back. Send people to checkout in \`onProceed\`, which runs once every required term is agreed.
- Pass your router's link through \`renderLink\`, like next/link, for the note's link, the button's href, and the review's links.
- AnnouncementIntro alone draws a still picture of the popup, for a page or a preview.
- The headline is set in the theme's \`font-headline\`. Load Geist Serif as \`--font-geist-serif\`, like \`geist/font/serif/variable\` in Next.js, or it falls back to the system serif.

## Avoid it for

- Opening it on every visit. Use \`showOnce\`, or open it yourself only when there is news.
- Showing it before people have seen the page, like on a first visit from an ad. Let them land first, and offer a trigger.
- More than four features. Link to a page that tells more.
- Checkboxes for terms. Each term is an Agreement, an "I agree" button that records who and when, so nothing is ticked by accident.
- Starting email updates on. The switch starts off, so nobody is signed up without choosing it.
- Hiding what costs extra. Put charges on top of the price last in the plan's \`perks\`, and as a term to agree to.
- A small yes or no question. Use Alert Dialog.
- A banner that stays on the page. Use Alert or Notice.

## Props

- `title`: `ReactNode`. Required. What is new, like "Introducing Plus". It names the dialog.
- `description`: `ReactNode`. One line under the title that says what it means for people.
- `label`: `ReactNode`. A short label between two lines over the features, like "Everything in one plan".
- `features`: `{ title: ReactNode; visual?: ReactNode; badge?: ReactNode }[]`. Two to four. Recommended: three. Each visual fills a 4:3 tile. A badge is a sticker on the tile, shown when the tiles sit in a row, from 36rem wide.
- `note`: `ReactNode`. One line in a soft box under the features, like a limit or a promise.
- `link`: `{ label: ReactNode; href: string }`. A link after the note, like See the guide.
- `action`: `{ label?: ReactNode; href?: string; onClick?: () => void }`. Default `{ label: "Try it" }`. The one main button. With a review step it moves to it. With no review and no href, it closes the dialog.
- `animated`: `boolean`. Default `true`. Recommended: true. Feature tiles lift on hover. Reduced motion keeps them still.
- `renderLink`: `(props: AnnouncementLinkProps) => ReactNode`. Default `a plain <a>`. Draws links. Spread the props onto your router's link: (props) => \<Link {...props} />.
- `trigger`: `ReactElement`. Recommended. The button that opens it, like \<Button emphasis="outline">What's new\</Button>.
- `showOnce`: `string`. Recommended for a launch. A storage key. It opens by itself once per browser, after the page loads, and not again after it closes. When storage is blocked it never opens by itself.
- `open`: `boolean`. Open it yourself. Use it with onOpenChange.
- `defaultOpen`: `boolean`. Default `false`. Open on first render, when you do not control it.
- `onOpenChange`: `(open: boolean) => void`. Called when it opens or closes.
- `review`: `ReactNode`. Recommended when the news leads to a purchase. A second step, like \<AnnouncementReview />.
- `container`: `HTMLElement | RefObject<HTMLElement>`. Where the dialog opens. By default, at the end of the page body.
- `className`: `string`. On AnnouncementDialog, classes for the popup, like max-w-lg. On AnnouncementIntro, classes for its root.
- `AnnouncementIntro`: `AnnouncementIntroProps`. Part. What the popup says, with no dialog: a still picture for a page or a preview. Takes the props from title to animated.
- `AnnouncementReview`: `AnnouncementReviewProps`. Part. The last step before checkout: account, terms, an email switch, and the plan as a card that turns over. Its props follow.
- `account`: `{ name: string; email: string; photo?: string; note?: ReactNode }`. AnnouncementReview. Required. Who is signing up. Their name goes on each agreement's record.
- `plan`: `AnnouncementPlan`. AnnouncementReview. Required. { name, price?, period?, note?, perks?, memberSince?, tagline?, marks? }. The front shows the price and perks. The back is a member card.
- `terms`: `{ id: string; title: ReactNode; text?: ReactNode; required?: boolean }[]`. AnnouncementReview. Terms to agree to, each with an "I agree" button. Required by default. Recommended: three or fewer.
- `updates`: `ReactNode | false`. Default `"Email me about new features."`. AnnouncementReview. A switch for email updates. It always starts off. Pass false to leave it out.
- `step`: `ReactNode`. AnnouncementReview. Where they are, like "Step 2 of 2".
- `links`: `{ label: ReactNode; href: string }[]`. AnnouncementReview. Links under the card, like See the guide and Get help.
- `onBack`: `() => void`. AnnouncementReview. Shows Back. Inside AnnouncementDialog, Back returns to the announcement by itself.
- `onProceed`: `(result: { agreed: Record<string, AgreementRecord>; updates: boolean }) => void`. AnnouncementReview. Runs once every required term is agreed. Before that, a press shows an error on each one left.
- `proceedLabel`: `ReactNode`. Default `"Proceed to checkout"`. AnnouncementReview. The main button's words.
- `termError`: `ReactNode`. Default `"Agree to this to go on."`. AnnouncementReview. Under each required term left unagreed.
- `loading`: `boolean`. Default `false`. AnnouncementReview. The proceed button shows a spinner, and the terms and switch are blocked.

## Accessibility

- The title names the dialog and the description describes it. Focus starts on the Close button, at the top.
- Escape, the Close button, and a press on the backdrop close it. Tab stays inside while it is open.
- Moving to the review step and back puts focus on the new step's title, so it never drops to the page.
- Feature pictures and badges are aria-hidden. The feature's title says what it is.
- With showOnce, it opens after the page loads, never during the first render, and only once.
- Each term is an Agreement: a group named by its title. A press on proceed with terms left moves focus to the first one, whose error is read with it.
- The turn button says what it does and is pressed while the back shows. The hidden face is inert, so screen readers and Tab skip it.
- In forced colors, tiles, the note, and the card's back get borders, so their edges still show.

## Examples

### Recommended: a launch with a review step

```tsx
<AnnouncementDialog
  title="Introducing Plus"
  description="One plan for the whole team: Schedule, Chat, and Stats."
  label="Everything in one plan"
  features={[
    { title: "Schedule, with who's coming", visual: <ScheduleMock />, badge: <Badge>24 going</Badge> },
    { title: "Chat and voice notes, by team", visual: <ChatMock /> },
    { title: "Stats and trends, all year", visual: <StatsMock /> },
  ]}
  note="Generous limits, shared across all three apps."
  link={{ label: "See the guide", href: "/plus" }}
  trigger={<Button emphasis="outline">What's new</Button>}
  showOnce="plus-launch"
  review={
    <AnnouncementReview
      step="Step 2 of 2"
      account={{ name: user.name, email: user.email }}
      terms={[{ id: "permanent", title: "Linking is permanent", text: "The linked apps cannot be split apart again." }]}
      plan={{ name: "Plus", price: "$12", period: "/month", perks: [{ title: "One sign-in for all three apps" }] }}
      onProceed={({ updates }) => startCheckout({ updates })}
    />
  }
/>
```

### News with no checkout

```tsx
<AnnouncementDialog
  title="Voice notes are here"
  description="Hold the mic in any team chat to send one."
  features={[{ title: "Chat and voice notes, by team", visual: <ChatMock /> }]}
  action={{ label: "Try it now", href: "/chat" }}
  showOnce="voice-notes"
  className="max-w-lg"
/>
```
