# Toast

> Toasts give short feedback after an action. Mount one Toaster, then call toast from anywhere.

Source: /docs/components/toast

```tsx
import { Toaster, toast } from '@aksornproject/aksara-ui'
```

## Basic toast

Mount `<Toaster />` once near the root of the app, then call `toast(message, options)`. `description` adds a second line. `toast` returns the toast id.

Sonner owns the queue, the timers, swipe to dismiss, the hover pause and the live region. Aksara UI supplies the colours, the icons, the layout and the action and close Buttons.

Each demo on this page mounts its own Toaster with an `id` and sends toasts to it with `toasterId`. An app with one Toaster needs neither.

```tsx
import { Button, Toaster, toast } from '@aksornproject/aksara-ui'

export default function ToastBasic() {
  return (
    <>
      <Button
        onClick={() =>
          toast.success('Changes saved', {
            description: 'Your profile is visible to your class.',
            toasterId: 'toast-basic',
          })
        }
      >
        Save profile
      </Button>
      <Toaster id="toast-basic" />
    </>
  )
}
```

## Tones

`toast`, `toast.success`, `toast.error`, `toast.warning` and `toast.information` pick the tone colour and icon. Choose by meaning. Success confirms, error reports a failure, warning flags a risk and information adds context.

```tsx
import { Button, Toaster, toast } from '@aksornproject/aksara-ui'

const options = { toasterId: 'toast-variants' }

export default function ToastVariants() {
  return (
    <>
      <Button
        variant="secondary-accent"
        onClick={() => toast('Lesson plan duplicated', options)}
      >
        Default
      </Button>
      <Button
        variant="secondary-accent"
        onClick={() => toast.success('Grades submitted', options)}
      >
        Success
      </Button>
      <Button
        variant="secondary-accent"
        onClick={() => toast.error('Upload failed', options)}
      >
        Error
      </Button>
      <Button
        variant="secondary-accent"
        onClick={() => toast.warning('Storage almost full', options)}
      >
        Warning
      </Button>
      <Button
        variant="secondary-accent"
        onClick={() => toast.information('Timetable updated', options)}
      >
        Information
      </Button>
      <Toaster id="toast-variants" />
    </>
  )
}
```

## Action

`action` adds a Button with a `label` and an `onClick`. A click dismisses the toast unless `onClick` calls `event.preventDefault()` synchronously. Use `duration: Infinity` for an action that must stay available, because keyboard focus does not pause the timer.

```tsx
import { Button, Toaster, toast } from '@aksornproject/aksara-ui'

export default function ToastAction() {
  function upload() {
    toast.error('Upload failed', {
      description: 'report-term-2.pdf could not be sent.',
      duration: Infinity,
      toasterId: 'toast-action',
      action: {
        label: 'Retry',
        onClick: () =>
          toast.success('Report uploaded', { toasterId: 'toast-action' }),
      },
    })
  }

  return (
    <>
      <Button onClick={upload}>Upload report</Button>
      <Toaster id="toast-action" />
    </>
  )
}
```

## Updating and dismissing

A call with the `id` of an open toast replaces it. An update keeps the native options it omits, so set `duration` again when you replace a persistent toast. `toast.dismiss(id)` closes it.

```tsx
import { Button, Toaster, toast } from '@aksornproject/aksara-ui'

export default function ToastUpdate() {
  function save() {
    toast('Saving draft', {
      id: 'draft',
      duration: Infinity,
      toasterId: 'toast-update',
    })
    setTimeout(
      () =>
        toast.success('Draft saved', {
          id: 'draft',
          duration: 4000,
          toasterId: 'toast-update',
        }),
      1500,
    )
  }

  return (
    <>
      <Button variant="secondary-accent" onClick={save}>
        Save draft
      </Button>
      <Toaster id="toast-update" />
    </>
  )
}
```

## Icon and close button

`icon` replaces the tone icon, and `icon: null` hides it. `closeButton: false` removes the close button. `closeButtonLabel` renames it, "Close toast" by default. `className` and `classNames` merge last, so your classes win.

```tsx
import { IconFoldersFolderLink } from '@aksornproject/aksara-icons'
import { Button, Toaster, toast } from '@aksornproject/aksara-ui'

export default function ToastOptions() {
  return (
    <>
      <Button
        variant="secondary-accent"
        onClick={() =>
          toast.information('Link copied', {
            icon: <IconFoldersFolderLink aria-hidden />,
            toasterId: 'toast-options',
          })
        }
      >
        Custom icon
      </Button>
      <Button
        variant="secondary-accent"
        onClick={() =>
          toast.warning('You are offline', {
            icon: null,
            closeButton: false,
            toasterId: 'toast-options',
          })
        }
      >
        No icon or close
      </Button>
      <Toaster id="toast-options" />
    </>
  )
}
```

## Position and width

`position`, `duration`, `visibleToasts`, `gap`, `offset` and `hotkey` pass through to Sonner. The `--width` style sets the desktop toast width, 542px by default. Below a 480px toast, and on phones, the action moves to its own row under the text.

```tsx
import { Button, Toaster, toast } from '@aksornproject/aksara-ui'

export default function ToastPosition() {
  return (
    <>
      <Button
        variant="secondary-accent"
        onClick={() =>
          toast('Attendance marked for 5/2', {
            toasterId: 'toast-position',
            action: { label: 'Undo', onClick: () => {} },
          })
        }
      >
        Mark attendance
      </Button>
      <Toaster
        id="toast-position"
        position="top-center"
        style={{ '--width': '420px' }}
      />
    </>
  )
}
```

## Accessibility

- Sonner's `<section aria-live="polite">` is the only live region. Toasts carry no `alert` or `status` role, so errors are announced politely too. Use a Dialog for anything that must interrupt.
- Icons are decorative. Put the meaning in the message.
- The action and close buttons are `type="button"`, so a Toaster inside a form never submits it. The action comes before the close in the tab order.
- Alt+T focuses the toast region and pauses the stack while it is expanded.
- Tab focus does not pause the timer. Give a toast with an action `duration: Infinity`.

## API

### Toaster

| Prop | Type | Description |
| --- | --- | --- |
| `id` | `string` |  |
| `position` | `"bottom-center" \| "bottom-left" \| "bottom-right" \| "top-center" \| "top-left" \| "top-right"` |  |
| `hotkey` | `string[]` |  |
| `expand` | `boolean` |  |
| `duration` | `number` |  |
| `gap` | `number` |  |
| `visibleToasts` | `number` |  |
| `className` | `string` |  |
| `offset` | `Offset` |  |
| `mobileOffset` | `Offset` |  |
| `dir` | `"auto" \| "ltr" \| "rtl"` |  |
| `swipeDirections` | `("bottom" \| "left" \| "right" \| "top")[]` |  |
| `containerAriaLabel` | `string` |  |
| `style` | `(CSSProperties & { '--width'?: string; })` | `--width` sets the desktop toast width. Defaults to 542px. |

### toast

`toast(message, options)` and each tone take these options.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `toasterId` | `string` |  |  |
| `description` | `(() => React.ReactNode) \| ReactNode` |  |  |
| `duration` | `number` |  |  |
| `onDismiss` | `((toast: ToastT) => void)` |  |  |
| `onAutoClose` | `((toast: ToastT) => void)` |  |  |
| `position` | `"bottom-center" \| "bottom-left" \| "bottom-right" \| "top-center" \| "top-left" \| "top-right"` |  |  |
| `testId` | `string` |  |  |
| `id` | `string \| number` |  |  |
| `icon` | `ReactElement<unknown, string \| React.JSXElementConstructor<any>> \| null` |  | Replaces the tone icon. `null` hides it. |
| `action` | `{ label: ReactNode; onClick: (event: MouseEvent<HTMLButtonElement>) => void }` |  | Adds an action button. A click dismisses the toast unless `onClick` prevents the default. |
| `closeButton` | `boolean` | `true` | Shows the close button. |
| `closeButtonLabel` | `string` | `'Close toast'` | The accessible name of the close button. |
| `className` | `string` |  | Merged last into the toast with `cn()`, so these classes win. |
| `classNames` | `{ title?: string; description?: string; closeButton?: string; actionButton?: string; content?: string; icon?: string }` |  | Classes merged last into each part of the toast. |

### ToastAction

| Prop | Type | Description |
| --- | --- | --- |
| `label` (required) | `ReactNode` | The action button text. |
| `onClick` (required) | `(event: MouseEvent<HTMLButtonElement>) => void` | Call `event.preventDefault()` synchronously to keep the toast open. |