<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Moment

A moment in an app's life, shown in the space it is given: nothing here yet, on its way, something failed, a first run, or something finished. It holds art (any node, usually an inline SVG), a title, one line and up to two actions. It renders in the page's flow and never floats over what the person is using; its celebration stays inside the art's frame.

```tsx
import { Moment } from "@/components/ouro/moment/moment";
```

## Example

```tsx
<Moment
  art={<EmptyInbox />}
  title="No messages yet"
  description="Messages you send show up here."
  action={<Button onClick={compose}>New message</Button>}
/>
<Moment kind="success" art={<Done />} title="All caught up" dismissKey="caught-up" cap={{ times: 1, per: "day" }} />
```

## Usage

Moments behave so they never get in the way: the art is decorative unless labelled, the art's own motion stops with reduced motion, the close button keeps a dismissal through `dismissKey`, `cap` limits how often a success celebrates, and `quiet` (or `MomentProvider`) makes them compact. The root carries `data-moment` (the kind), `data-celebrate` while it celebrates and `data-quiet` when quiet, and the art `data-moment-art` (its `artName`).

## Use instead when

- Use `Empty` instead for a plain empty list with an icon and no art, `Alert` for a message about something that happened beside the content, `Skeleton` for the shape of content that is loading, and `toast()` to confirm an everyday action.

## Props

Type: `MomentProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `action` | `ReactNode` |  | The way forward, usually one `Button`: `action={<Button onClick={compose}>New message</Button>}`. |
| `announce` | `boolean` |  | Speaks a `success`, `error` or `ask` moment politely to screen readers when it appears: the title, then the description when it is text. Other kinds are never spoken. |
| `art` | `ReactNode` |  | The picture: any node, usually an inline SVG, which fills the art's height (160px, 96px when the moment is narrower than 24rem, 24px inline; none when quiet). It is decorative and hidden from screen readers unless you pass `artLabel`. Draw it with `currentColor` and the theme's variables so it follows the kind, the theme and dark mode. With reduced motion, its CSS animations stop and its SVG animations pause. Its motion has a budget too: its loops settle at the end of their turn about 5 s after it appears, and with a `dismissKey` or `id` its entrance plays once a session, so a moment that comes back as people switch views or filters arrives still. |
| `artLabel` | `string` |  | Names the art for screen readers (`role="img"`), for a picture that says something the title does not. |
| `artName` | `string` |  | Which picture this is, such as a pose ("waving", "pedalling"), for checkers, tests and styles: the art carries it as `data-moment-art`, which it has, empty, whenever there is art. Not shown or spoken. |
| `cap` | `{ times: number; per: "day" \| "week" \| "month" \| "ever" \| (string & { 'Use "day", "week", "month" or "ever"': never }) }` |  | Shows the flourish at most `times` times `per` day, week, 30-day month or ever, counted in `localStorage` under the moment's `dismissKey`, or its `id`; after that the moment is quiet. Without a key the flourish shows every time the moment appears. |
| `celebrate` | `boolean` | `true` for `kind="success"`, otherwise `false` | A short flourish when the moment appears: a soft disc swells from behind the art and four sparks grow and fade, all inside the art's frame. With reduced motion only the disc fades. Never when quiet or inline. Capped with `cap`. |
| `className` | `string` |  | Layout only: margins, width, grid placement (`mt-6`). Appearance classes warn in development; use `kind` and `quiet`. |
| `description` | `ReactNode` |  | One line on what to do or what it means: "Messages you send show up here." |
| `dismissFor` | `"month" \| "hour" \| "day" \| "week"` |  | How long a dismissal through `dismissKey` lasts: an hour, a day, a week or 30 days; then the moment may show again. Without it a dismissal lasts for good. For an ask that may come back later, but not straight away. |
| `dismissKey` | `string` |  | Keeps a dismissal: once the person closes the moment, it stays closed on every visit and in every tab, stored in `localStorage` as `ouro-moment:dismissed:<dismissKey>`. Shows the close button. With it the moment renders nothing until the browser has said whether it was dismissed, so a server-rendered page never flashes a closed moment. |
| `dismissLabel` | `string` | `"Dismiss"` | The close button's accessible name, for other languages. |
| `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `3`, and no heading with `size="inline"` | The title's heading level. Pick the level below the section the moment sits in. Inline, the title is plain text unless you pass one. |
| `kind` | `"loading" \| "success" \| "error" \| "empty" \| "first-run" \| "welcome" \| "ask"` | `"empty"` | What the moment is about. `empty`: nothing here yet. `loading`: on its way, with a spinner when there is no art. `error`: something failed. `first-run`: a first run or a welcome (`welcome` is the same kind by its older name, and the root says `first-run`). `success`: something finished, which celebrates (see `celebrate`). `ask`: something the app, or an agent working for the person, asks them to look at; show it with `size="inline"` in a slot of its own, such as a header strip or a notices area, never over the content. The kind colours the art's `currentColor` from the theme and is on the root as `data-moment`. |
| `onDismiss` | `(() => void)` |  | Shows a quiet close button. Pressing it hides the moment, hands focus to what follows it, and calls this. |
| `quiet` | `boolean` | the nearest `MomentProvider`'s `quiet`, else `OuroProvider`'s, else `false` | Words only, in a compact row: the title and one line, with no art and no celebration (a loading moment keeps its small spinner). For dense panes, and for people who want less: `OuroProvider`'s `quiet` sets it for the whole app, and `MomentProvider` for a part of it. |
| `ref` | `Ref<HTMLDivElement>` |  | Reaches the rendered element. |
| `secondaryAction` | `ReactNode` |  | A second, quieter way, beside `action`: `secondaryAction={<Button variant="plain">Learn more</Button>}`. Two at most. |
| `size` | `"inline" \| "default" \| "hero"` | `"default"` | `default` stacks the art, title, line and actions in the space it is given. `hero` does too, with art up to 220px tall, for the main state of a large, otherwise empty pane; narrower than 24rem it holds at 96px, as `default` does. `inline` is a single row with 24px art, for a "working" row in a list or toolbar; in a slot too narrow for its words and its actions, the actions move under the words. |
| `title` (required) | `string` |  | What happened or what is here, in a few words: "Inbox cleared". Required, because it is what a screen reader finds. |

## Native attributes

Also accepts the attributes in React's `HTMLAttributes` (`id`, `aria-*`, event handlers), passed to the rendered element, except those named on this page.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `actions`: Not a prop. Ouro UI uses `action` and `secondaryAction`: `<Moment action={<Button>Retry</Button>}>`.
- `body`: Not a prop. Ouro UI uses `description`: `<Moment description="Messages you send show up here.">`.
- `heading`: Not a prop. Ouro UI uses `title`: `<Moment title="No messages yet">`.
- `icon`: Not a prop. Ouro UI uses `art`: `<Moment art={<Illustration />}>`.
- `illustration`: Not a prop. Ouro UI uses `art`: `<Moment art={<Illustration />}>`.
- `onClose`: Not a prop. Ouro UI uses `onDismiss`: `<Moment onDismiss={() => setOpen(false)}>`.
- `variant`: Not a prop. Ouro UI uses `kind`: `<Moment kind="error">`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: Moment merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

[^layout-classes]: test:registry/ouro/components/ouro/moment/moment.test.tsx::Moment > passes attributes and a ref through, and lets layout classes win verify:testExists(registry/ouro/components/ouro/moment/moment.test.tsx::Moment > passes attributes and a ref through, and lets layout classes win)
