# Toast

A succinct message that is displayed temporarily.

```twig
<div style="min-height: 240px">
    <twig:Toaster>
        <twig:Button
            variant="outline"
            data-action="click->toast#add"
            data-toast-title-param="Event created"
            data-toast-description-param="Sunday, December 3 at 9:00 AM"
            data-toast-action-label-param="Undo"
        >
            Show Toast
        </twig:Button>
    </twig:Toaster>
</div>
```

## Installation

> [!NOTE]
> Available since UX Toolkit 3.6.

```shell
php bin/console ux:install toast --kit shadcn
```
> [!NOTE]
> If you installed the `sonner` recipe before Symfony UX Toolkit 3.6, your app already has a `templates/components/Toast.html.twig` file, which is `Sonner`'s toast. Installing `toast` asks to overwrite that file, and overwriting it makes the `<twig:Toast>` tags you use with `Sonner` render this new component instead.
>
> To keep both, move that file to `templates/components/Sonner/Toast.html.twig` before installing `toast`, then replace `<twig:Toast` with `<twig:Sonner:Toast` in your app's templates.

## Usage

Render `Toaster` once, usually in your base layout, and set `position` to the corner the toasts should appear in. Then fire toasts from any element with a `toast#add` action, configured with `data-toast-<name>-param` attributes: `type`, `title`, `description`, `duration`, `id` and `actionLabel`.

```twig
<twig:Toaster position="top-left | top-center | top-right | bottom-left | bottom-center | bottom-right" duration="5000" limit="3">
    <twig:Button
        data-action="click->toast#add"
        data-toast-type-param="default | success | info | warning | error | loading"
        data-toast-title-param="Event created"
        data-toast-description-param="Sunday, December 3 at 9:00 AM"
        data-toast-action-label-param="Undo"
    >
        Show Toast
    </twig:Button>
</twig:Toaster>
```

The controller also exports a `toast()` helper, so application code can raise a toast without a trigger element: `toast.success('Event created')`, `toast.error('Could not create event.')`, or `toast.promise(request, { loading: 'Creating event…', success: 'Event created.' })`.

## Examples

### Types

Set `type` to render a status icon. The built-in renderer recognizes `success`, `info`, `warning`, `error` and `loading`.

```twig
<div style="min-height: 320px">
    <twig:Toaster>
        <div class="flex flex-wrap gap-2">
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-description-param="Event has been created."
            >Default</twig:Button>
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-type-param="success"
                data-toast-description-param="Event has been created."
            >Success</twig:Button>
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-type-param="info"
                data-toast-description-param="Arrive 10 minutes before the event."
            >Info</twig:Button>
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-type-param="warning"
                data-toast-description-param="The event cannot start before 8:00 AM."
            >Warning</twig:Button>
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-type-param="error"
                data-toast-description-param="The event could not be created."
            >Error</twig:Button>
        </div>
    </twig:Toaster>
</div>
```

### With Action

Pass `actionLabel` to render an action button. Clicking it dismisses the toast and emits a `toast:close` event whose `detail` carries the toast `id` and a `reason` of `action`, so your app can tell an "Undo" click apart from the other ways a toast closes: `close`, `escape`, `swipe`, and `timeout`.

```twig
<div style="min-height: 240px">
    <twig:Toaster>
        <twig:Button
            variant="outline"
            data-action="click->toast#add"
            data-toast-title-param="Event created"
            data-toast-description-param="You can undo this action."
            data-toast-action-label-param="Undo"
        >
            Show Toast
        </twig:Button>
    </twig:Toaster>
</div>
```

### Promise

Use the `toast#promise` action to update one toast as an asynchronous task moves through loading, success and error states.

```twig
<div style="min-height: 280px">
    <twig:Toaster>
        <div class="flex flex-wrap gap-2">
            <twig:Button
                variant="outline"
                data-action="click->toast#promise"
                data-toast-loading-param="Creating event…"
                data-toast-success-param="Event created."
            >Create Event</twig:Button>
            <twig:Button
                variant="outline"
                data-action="click->toast#promise"
                data-toast-loading-param="Creating event…"
                data-toast-error-param="Could not create event."
                data-toast-reject-param="true"
            >Create Event (Failing)</twig:Button>
        </div>
    </twig:Toaster>
</div>
```

### Position

Set `position` on `Toaster` to anchor the region to one of the six corners. Below the `sm` breakpoint the region spans the available width, so only the vertical side applies there.

```twig
<div style="min-height: 360px">
    <div class="flex flex-wrap justify-center gap-2">
        <twig:Toaster position="top-left">
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-title-param="Top left notification"
            >Top Left</twig:Button>
        </twig:Toaster>
        <twig:Toaster position="top-center">
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-title-param="Top center notification"
            >Top Center</twig:Button>
        </twig:Toaster>
        <twig:Toaster position="top-right">
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-title-param="Top right notification"
            >Top Right</twig:Button>
        </twig:Toaster>
        <twig:Toaster position="bottom-left">
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-title-param="Bottom left notification"
            >Bottom Left</twig:Button>
        </twig:Toaster>
        <twig:Toaster position="bottom-center">
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-title-param="Bottom center notification"
            >Bottom Center</twig:Button>
        </twig:Toaster>
        <twig:Toaster position="bottom-right">
            <twig:Button
                variant="outline"
                data-action="click->toast#add"
                data-toast-title-param="Bottom right notification"
            >Bottom Right</twig:Button>
        </twig:Toaster>
    </div>
</div>
```

### Server Rendered

Fill the `toasts` block of `Toaster` to show items on page load, for instance by looping over `app.flashes`. Give them `duration="0"` to keep them until they are dismissed.

`Toast` items dropped into `data-slot="toast-viewport"` after the page has loaded, by a Turbo Stream for instance, are picked up and animated in the same way. Use `prepend` rather than `append` so the newest one lands in front of the stack.

```twig
<div style="min-height: 240px">
    <twig:Toaster>
        <twig:block name="toasts">
            <twig:Toast type="success" duration="0">
                <twig:Toast:Content>
                    <twig:Toast:Icon type="success" />
                    <div class="flex min-w-0 flex-1 flex-col gap-1">
                        <twig:Toast:Title>Profile saved</twig:Toast:Title>
                        <twig:Toast:Description>Your changes have been applied.</twig:Toast:Description>
                    </div>
                    <twig:Toast:Close />
                </twig:Toast:Content>
            </twig:Toast>
            <twig:Toast type="info" duration="0">
                <twig:Toast:Content>
                    <twig:Toast:Icon type="info" />
                    <div class="flex min-w-0 flex-1 flex-col gap-1">
                        <twig:Toast:Title>Welcome back, Alice!</twig:Toast:Title>
                    </div>
                    <twig:Toast:Close />
                </twig:Toast:Content>
            </twig:Toast>
        </twig:block>
    </twig:Toaster>
</div>
```

### RTL

To enable RTL support, set the `dir="rtl"` attribute on the root element.

```twig
<div style="min-height: 300px">
    <twig:Toaster dir="rtl">
        <twig:block name="toasts">
            <twig:Toast type="success" duration="0">
                <twig:Toast:Content>
                    <twig:Toast:Icon type="success" />
                    <div class="flex min-w-0 flex-1 flex-col gap-1">
                        <twig:Toast:Title>تم إنشاء الحدث</twig:Toast:Title>
                        <twig:Toast:Description>الأحد ٣ ديسمبر الساعة ٩:٠٠ صباحًا</twig:Toast:Description>
                    </div>
                    <twig:Toast:Close label="إغلاق الإشعار" />
                </twig:Toast:Content>
            </twig:Toast>
        </twig:block>
    </twig:Toaster>
    <twig:Toaster dir="rtl" class="bottom-36">
        <twig:block name="toasts">
            <twig:Toast type="info" duration="0">
                <twig:Toast:Content>
                    <twig:Toast:Icon type="info" />
                    <div class="flex min-w-0 flex-1 flex-col gap-1">
                        <twig:Toast:Title>האירוע נוצר</twig:Toast:Title>
                        <twig:Toast:Description>יום ראשון, 3 בדצמבר בשעה 9:00</twig:Toast:Description>
                    </div>
                    <twig:Toast:Close label="סגור התראה" />
                </twig:Toast:Content>
            </twig:Toast>
        </twig:block>
    </twig:Toaster>
</div>
```

## Accessibility

- `Toaster` renders the toast region as a fixed container that is itself the live region (`aria-live="polite"`), so a toast is read out as it lands without stealing focus from whatever the user is doing. A live region has to be in the DOM before a node arrives inside it, which is why the `aria-live` sits on the region rather than on the toast: a toast carrying its own is never announced.
- The region is a landmark (`role="region"`) named by the `label` prop of `Toaster`, which defaults to `Notifications`. Translate it when your interface is not in English.
- A `Toast` of type `error` escalates out of that politeness with `role="alert"` and `aria-live="assertive"`, so a failure interrupts while ordinary confirmations wait their turn.
- Each toast is `aria-atomic="true"`, so its title and description are announced together rather than piecemeal, while the region is `aria-atomic="false"` so a new toast does not re-announce the whole stack.
- The status icon is `aria-hidden="true"`: it repeats what the text already says. Never rely on it alone to carry the meaning of a toast; write the type into the copy.
- `Toast:Close` carries a translatable `label` prop, rendered as its `aria-label`. Translate it when your interface is not in English.
- Auto-dismiss timers pause while the region is hovered **or** holds keyboard focus, so a toast cannot disappear from under someone tabbing into its action button.
- Escape dismisses the toast that currently holds keyboard focus. Toasts beyond the `limit` stay `inert`, so Tab skips over them while they remain hidden.
- Give `duration` enough time to read the toast, and do not put the only copy of important information in one. A toast disappears and cannot be recalled. An action inside it is reachable by tabbing, but only while it is on screen, so keep the same action available elsewhere in the page.

## API Reference

### `<twig:Toast>`

| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `type` | `'default'\|'success'\|'info'\|'warning'\|'error'\|'loading'` | `'default'` | The toast type, which selects the status icon and the announcement politeness. |
| `id` | `string\|null` | `null` | Stable identifier; adding a toast with the same one replaces it instead of stacking a new one. |
| `duration` | `int\|null` | `null` | Auto-dismiss delay in milliseconds, where `0` keeps the toast until it is dismissed. |

| Block | Description |
|:------|:------------|
| `content` | The toast body, typically a `Toast:Content`. |
### `<twig:Toast:Action>`

| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `variant` | `'default'\|'secondary'\|'destructive'\|'outline'\|'ghost'\|'link'` | `'outline'` | The button style variant. |
| `size` | `'default'\|'xs'\|'sm'\|'lg'\|'icon'\|'icon-xs'\|'icon-sm'\|'icon-lg'` | `'sm'` | The button size. |
| `as` | `'button'\|'a'` | `'button'` | The HTML tag to render; use `a` when the action navigates. |

| Block | Description |
|:------|:------------|
| `content` | The action button label. |
### `<twig:Toast:Close>`

| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `label` | `string` | `'Close toast'` | Accessible label of the close button; translate it when your interface is not in English. |
### `<twig:Toast:Content>`

| Block | Description |
|:------|:------------|
| `content` | The toast content, typically a `Toast:Icon`, a `Toast:Title`, a `Toast:Description`, a `Toast:Action` and a `Toast:Close`. |
### `<twig:Toast:Description>`

| Block | Description |
|:------|:------------|
| `content` | The secondary line describing the toast. |
### `<twig:Toast:Icon>`

| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `type` | `'default'\|'success'\|'info'\|'warning'\|'error'\|'loading'` | `'default'` | The toast type whose status icon is rendered; `default` renders nothing. |
### `<twig:Toast:Title>`

| Block | Description |
|:------|:------------|
| `content` | The toast title. |
### `<twig:Toaster>`

| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `position` | `'top-left'\|'top-center'\|'top-right'\|'bottom-left'\|'bottom-center'\|'bottom-right'` | `'bottom-right'` | Corner of the screen the toast region is anchored to. |
| `duration` | `int` | `5000` | Auto-dismiss delay in milliseconds applied to toasts that do not set their own, where `0` keeps them until dismissed. |
| `limit` | `int` | `3` | Maximum number of toasts kept on screen; the ones beyond it fade out until a slot frees up. |
| `label` | `string` | `'Notifications'` | Accessible name of the toast region; translate it when your interface is not in English. |

| Block | Description |
|:------|:------------|
| `toasts` | The `Toast` items rendered on page load, for instance by looping over `app.flashes`. |
| `content` | The trigger element(s) firing `toast#add`. |
### `data-controller="toast"`

| Value | Type | Default | Description |
|:------|:-----|:--------|:------------|
| `data-toast-duration-value` | `Number` | `5000` | Auto-dismiss delay in milliseconds for toasts that do not carry their own. |
| `data-toast-limit-value` | `Number` | `3` | Maximum number of toasts kept on screen. |

| Target | Description |
|:-------|:------------|
| `viewport` | The region toasts are stacked into. |
| `template` | The markup cloned for every toast created from JavaScript. |

| Action | Description |
|:-------|:------------|
| `add` | Creates (or replaces) a toast from the `data-toast-*-param` attributes. |
| `promise` | Shows a loading toast, then replaces it with the success or error one. |
| `close` | Dismisses the toast the event originated from. |
| `closeAll` | Dismisses every toast at once. |