# Popups and banners — `@sonordev/site-kit/website/popups`

The popups, banners and toasts you publish in Sonor (Website → Popups &
Banners), shown on your site where and when you set them, in the site's own
design.

## Usage

`SiteKitLayout` mounts it for you, after the page goes idle:

```tsx
<SiteKitLayout>{children}</SiteKitLayout>                  // popups on (default)
<SiteKitLayout popups={false}>{children}</SiteKitLayout>   // no popups
```

On a site without `SiteKitLayout`, mount `SitePopups` the same way:

```tsx
'use client'
import { SitePopups } from '@sonordev/site-kit/website/popups'

export function Popups() {
  return <SitePopups />
}
```

A project with nothing published costs one request and draws nothing. The
renderer downloads only when a popup is about to open.

## Props

```ts
interface SitePopupsProps {
  apiUrl?: string          // Default: from SiteKitLayout, else https://api.sonor.io
  apiKey?: string          // Default: from SiteKitLayout
  zIndex?: number          // The layer popups, banners and toasts sit on. Default: 9999
  allowInFrame?: boolean   // Show inside a cross-origin frame. Default: false
  allowLocalhost?: boolean // Show on localhost. Default: false
  debug?: boolean
}
```

Through `SiteKitLayout`, `zIndex` comes from `chat={{ zIndex }}`, so the chat
launcher and popups share one layer.

## What shows

Each popup is a set of blocks (heading, formatted text, image, button,
divider) drawn in your site's design: its colors, fonts, radius and buttons.
`SiteKitLayout` measures the design from the home page at idle and reports it
to Sonor, so the popup builder previews popups the way they'll look. Its
`design` prop declares any part of it yourself
(`design={{ primary: 'var(--brand)', fontHeading: 'Fraunces, serif' }}`).

| Placement | What it is                                                                       |
| --------- | -------------------------------------------------------------------------------- |
| Popup     | Centred over a dimmed page. One at a time: a second waits until the first closes |
| Banner    | A bar along the top or bottom edge                                               |
| Toast     | A small card in a corner (slide-in)                                              |

## Targeting and triggers

All set in Sonor, no code changes:

- **Pages**: include and exclude paths, with `*` for a prefix (`/services/*`)
- **Devices**: desktop, mobile, tablet
- **Schedule**: start and end dates
- **Trigger**: immediately, after a delay, at a scroll depth, or on exit intent
- **Frequency**: once, once per session, or every N days. Closing a popup and
  pressing its button both count as seen.

A popup targeted at one page leaves when the visitor navigates away from it.

### Popups wait for forms

A popup or toast that opens on its own first checks whether the visitor is in
the middle of a form, and waits while they are. Mid-form means:

- focus is in a form field, or in a card field embedded in a form;
- a form has a field they've filled in and haven't sent yet;
- a site-kit form is sending.

It opens a moment after focus leaves the form, or once the form has been sent
and cleared. It never gives up: while the visitor stays mid-form, it keeps
waiting. Banners show at once, since they don't take focus or hide the page.

Why it matters: a modal popup takes focus and hides the rest of the page from
assistive technology while it's open. Opening one mid-form throws a person out
of the field they're typing in, and an AI agent browser filling the form for
someone (agents act on the accessibility tree) loses every field it was about
to fill.

## Accessibility

Popups, toasts and banners are built so people and agent browsers can find
them and their controls by name:

- **A popup** is a modal dialog (`role="dialog"`, `aria-modal="true"`) named
  by its heading. It takes focus when it opens, keeps Tab inside, closes on
  Escape or a click outside it, and puts focus back where it was.
- **A toast** is a named, non-modal dialog, and **a banner** is a named
  region. Both close on Escape and leave focus where it is.
- **Close buttons are named "Close"**, whatever they show.

## Tracking

Each popup's impression (once half of it is on screen) and button presses are
counted in Sonor, against the same visitor ID (`_sk_vid`) analytics uses.

## Drawing one popup yourself

`PopupBlocks` is the renderer `SitePopups` uses, for drawing one popup from
its blocks (a preview, a custom placement):

```tsx
import { PopupBlocks } from '@sonordev/site-kit/website/popups'

<PopupBlocks
  blocks={[{ type: 'heading', text: 'Spring cleanups are booking' }]}
  placement="popup"
  onClose={() => setOpen(false)}
/>
```

`inline` renders it in place, without the overlay or focus handling.

## Engage (retired)

Engage was retired in Sonor, and its popups with it. Popups made in Engage
Studio (a `design_json` without blocks) aren't drawn by site-kit 7.2 or later;
make them again in Website → Popups & Banners. `@sonordev/site-kit/engage`
still builds through 7.x: its `EngageWidget` draws `SitePopups` and the
website chat.
