# Toast

URL: https://ark-ui.com/docs/components/toast
Source: https://raw.githubusercontent.com/chakra-ui/ark/refs/heads/main/website/src/content/pages/components/toast.mdx

A message that appears on the screen to provide feedback on an action.

---

<ComponentPreview id="Toast" />

## Anatomy

<Anatomy id="toast" />

```tsx
const toaster = createToaster({ placement: 'bottom-end' })

<Toaster toaster={toaster}>
  {(toast) => (
    <Toast.Root>
      <Toast.Title />
      <Toast.Description />
      <Toast.ActionTrigger />
      <Toast.CloseTrigger />
    </Toast.Root>
  )}
</Toaster>
```

## Setup

To use the Toast component, create the toast engine using the `createToaster` function.

This function manages the placement and grouping of toasts, and provides a `toast` object needed to create toast
notification.

```ts
const toaster = createToaster({
  placement: 'bottom-end',
  overlap: true,
  gap: 24,
})
```

## Examples

Here's an example of creating a toast using the `toast.create` method.

**Example: basic**

```ripple
import { Toast, Toaster, createToaster } from 'ark-ripple/toast';
import { Portal } from 'ark-ripple/portal';
import { X } from 'lucide-ripple';
import button from 'styles/button.module.css';
import styles from 'styles/toast.module.css';

const toaster = createToaster(
  {
    placement: 'bottom-end',
    overlap: true,
    gap: 24,
  },
);

export component Basic() {
  <div>
    <button
      type="button"
      class={button.Root}
      onClick={() => {
        toaster.create(
          {
            title: 'Scheduled for tomorrow',
            description: 'Your meeting has been scheduled for tomorrow at 10am.',
            type: 'info',
          },
        );
      }}
    >
      {'Schedule meeting'}
    </button>
    <Portal>
      <Toaster {toaster}>
        component children({ toast }) {
          <Toast.Root class={styles.Root}>
            <Toast.Title class={styles.Title}>{@toast.title}</Toast.Title>
            <Toast.Description class={styles.Description}>{@toast.description}</Toast.Description>
            <Toast.CloseTrigger class={styles.CloseTrigger}>
              <X />
            </Toast.CloseTrigger>
          </Toast.Root>
        }
      </Toaster>
    </Portal>
  </div>
}

```

### Types

You can create different types of toasts (`success`, `error`, `warning`, `info`) with appropriate styling. For example,
to create a success toast, you can do:

```ts
toaster.success({
  title: 'Success!',
  description: 'Your changes have been saved.',
})
```

**Example: types**

```ripple
import { Toast, Toaster, createToaster } from 'ark-ripple/toast';
import { Portal } from 'ark-ripple/portal';
import { CircleAlert, TriangleAlert, CircleCheck, Info, X } from 'lucide-ripple';
import button from 'styles/button.module.css';
import styles from 'styles/toast.module.css';

const toaster = createToaster(
  {
    overlap: true,
    placement: 'bottom-end',
    gap: 16,
  },
);

const iconMap = {
  success: CircleCheck,
  error: CircleAlert,
  warning: TriangleAlert,
  info: Info,
};

export component Types() {
  <div>
    <div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
      <button
        type="button"
        class={button.Root}
        onClick={() => {
          toaster.success(
            { title: 'Changes saved', description: 'Your profile has been updated successfully.' },
          );
        }}
      >
        {'Success'}
      </button>
      <button
        type="button"
        class={button.Root}
        onClick={() => {
          toaster.error(
            { title: 'Upload failed', description: 'There was an error uploading your file.' },
          );
        }}
      >
        {'Error'}
      </button>
      <button
        type="button"
        class={button.Root}
        onClick={() => {
          toaster.warning(
            { title: 'Low storage', description: 'You have less than 10% storage remaining.' },
          );
        }}
      >
        {'Warning'}
      </button>
      <button
        type="button"
        class={button.Root}
        onClick={() => {
          toaster.info(
            {
              title: 'Update available',
              description: 'A new version of the app is ready to install.',
            },
          );
        }}
      >
        {'Info'}
      </button>
    </div>

    <Portal>
      <Toaster {toaster}>
        component children({ toast }) {
          const ToastIcon = @toast.type ? iconMap[@toast.type as keyof typeof iconMap] : undefined;
          <Toast.Root class={styles.Root}>
            <Toast.Title class={styles.Title}>
              if (ToastIcon) {
                <ToastIcon class={styles.Indicator} />
              }
              {@toast.title}
            </Toast.Title>
            <Toast.Description class={styles.Description}>{@toast.description}</Toast.Description>
            <Toast.CloseTrigger class={styles.CloseTrigger}>
              <X />
            </Toast.CloseTrigger>
          </Toast.Root>
        }
      </Toaster>
    </Portal>
  </div>
}

```

### Promise

You can use `toaster.promise()` to automatically handle the different states of an asynchronous operation. It provides
options for the `success`, `error`, and `loading` states of the promise and will automatically update the toast when the
promise resolves or rejects.

**Example: promise-toast**

```ripple
import { Toast, Toaster, createToaster } from 'ark-ripple/toast';
import { Portal } from 'ark-ripple/portal';
import { X, Loader, CircleCheck, CircleAlert } from 'lucide-ripple';
import button from 'styles/button.module.css';
import styles from 'styles/toast.module.css';

const toaster = createToaster(
  {
    overlap: true,
    placement: 'bottom-end',
    gap: 16,
  },
);

const uploadFile = () => {
  return new Promise<void>((resolve, reject) => {
    setTimeout(() => {
      Math.random() > 0.5 ? resolve() : reject(new Error('Upload failed'));
    }, 2000);
  });
};

export component PromiseToast() {
  const handleUpload = async () => {
    toaster.promise(uploadFile, {
      loading: {
        title: 'Uploading file...',
        description: 'Please wait while we upload your document.',
      },
      success: {
        title: 'Upload complete',
        description: 'Your file has been uploaded successfully.',
      },
      error: {
        title: 'Upload failed',
        description: 'Could not upload the file. Please try again.',
      },
    });
  };

  <div>
    <button type="button" class={button.Root} onClick={handleUpload}>{'Upload file'}</button>

    <Portal>
      <Toaster {toaster}>
        component children({ toast }) {
          <Toast.Root class={styles.Root}>
            <Toast.Title class={styles.Title}>
              if (@toast.type === 'loading') {
                <Loader class={styles.Indicator} data-type="loading" />
              } else if (@toast.type === 'success') {
                <CircleCheck class={styles.Indicator} />
              } else if (@toast.type === 'error') {
                <CircleAlert class={styles.Indicator} />
              }
              {@toast.title}
            </Toast.Title>
            <Toast.Description class={styles.Description}>{@toast.description}</Toast.Description>
            <Toast.CloseTrigger class={styles.CloseTrigger}>
              <X />
            </Toast.CloseTrigger>
          </Toast.Root>
        }
      </Toaster>
    </Portal>
  </div>
}

```

### Update

To update a toast, use the `toast.update` method.

**Example: update**

```ripple
import { track } from 'ripple';
import { Toast, Toaster, createToaster } from 'ark-ripple/toast';
import { Portal } from 'ark-ripple/portal';
import button from 'styles/button.module.css';
import styles from 'styles/toast.module.css';

const toaster = createToaster(
  {
    placement: 'bottom-end',
    overlap: true,
    gap: 24,
  },
);

export component Update() {
  let id = track<string | undefined>(undefined);

  const createToast = () => {
    @id = toaster.create(
      {
        title: 'Sending message...',
        description: 'Please wait while we deliver your message.',
        type: 'loading',
      },
    );
  };

  const updateToast = () => {
    if (!@id) {
      return;
    }
    toaster.update(@id, {
      title: 'Message sent',
      description: 'Your message has been delivered successfully.',
      type: 'success',
    });
  };

  <div class="hstack">
    <button type="button" class={button.Root} onClick={createToast}>{'Send message'}</button>
    <button type="button" class={button.Root} onClick={updateToast}>{'Mark as sent'}</button>
    <Portal>
      <Toaster {toaster}>
        component children({ toast }) {
          <Toast.Root class={styles.Root}>
            <Toast.Title class={styles.Title}>{@toast.title}</Toast.Title>
            <Toast.Description class={styles.Description}>{@toast.description}</Toast.Description>
          </Toast.Root>
        }
      </Toaster>
    </Portal>
  </div>
}

```

### Action

To add an action to a toast, use the `toast.action` property.

**Example: action**

```ripple
import { Toast, Toaster, createToaster } from 'ark-ripple/toast';
import { Portal } from 'ark-ripple/portal';
import button from 'styles/button.module.css';
import styles from 'styles/toast.module.css';

const toaster = createToaster(
  {
    placement: 'bottom-end',
    gap: 24,
  },
);

export component Action() {
  <div>
    <button
      type="button"
      class={button.Root}
      onClick={() => {
        toaster.create(
          {
            title: 'Event has been created',
            description: 'We have sent you an email with the event details.',
            type: 'info',
            action: {
              label: 'Undo',
              onClick: () => {
                console.log('Undo');
              },
            },
          },
        );
      }}
    >
      {'Create event'}
    </button>
    <Portal>
      <Toaster {toaster}>
        component children({ toast }) {
          <Toast.Root class={styles.Root}>
            <Toast.Title class={styles.Title}>{@toast.title}</Toast.Title>
            <Toast.Description class={styles.Description}>{@toast.description}</Toast.Description>
            if (@toast.action) {
              <Toast.ActionTrigger class={styles.ActionTrigger}>
                {@toast.action?.label}
              </Toast.ActionTrigger>
            }
          </Toast.Root>
        }
      </Toaster>
    </Portal>
  </div>
}

```

### Duration

You can control how long a toast stays visible by setting a custom `duration` in milliseconds, or use `Infinity` to keep
it visible until manually dismissed.

**Example: duration**

```ripple
import { Toast, Toaster, createToaster } from 'ark-ripple/toast';
import { Portal } from 'ark-ripple/portal';
import { X, Clock } from 'lucide-ripple';
import button from 'styles/button.module.css';
import styles from 'styles/toast.module.css';

const toaster = createToaster(
  {
    overlap: true,
    placement: 'bottom-end',
    gap: 16,
  },
);

const durations = [
  { label: '1s', value: 1000 },
  { label: '3s', value: 3000 },
  { label: '5s', value: 5000 },
  { label: '∞', value: Infinity },
];

export component Duration() {
  <div>
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
      for (const duration of durations; key duration.label) {
        <button
          type="button"
          class={button.Root}
          onClick={() => {
            toaster.create(
              {
                title: 'Reminder set',
                description: `This notification will ${
                  duration.value === Infinity
                    ? 'stay until dismissed'
                    : `disappear in ${duration.label}`
                }.`,
                type: 'info',
                duration: duration.value,
              },
            );
          }}
        >
          {duration.label}
        </button>
      }
    </div>

    <Portal>
      <Toaster {toaster}>
        component children({ toast }) {
          <Toast.Root class={styles.Root}>
            <Toast.Title class={styles.Title}>
              <Clock class={styles.Indicator} />
              {@toast.title}
            </Toast.Title>
            <Toast.Description class={styles.Description}>{@toast.description}</Toast.Description>
            <Toast.CloseTrigger class={styles.CloseTrigger}>
              <X />
            </Toast.CloseTrigger>
          </Toast.Root>
        }
      </Toaster>
    </Portal>
  </div>
}

```

### Max Visible

Set the `max` prop on the `createToaster` function to define the maximum number of toasts that can be rendered at any
one time. Any extra toasts will be queued and rendered when a toast has been dismissed.

**Example: max-toasts**

```ripple
import { Toast, Toaster, createToaster } from 'ark-ripple/toast';
import { Portal } from 'ark-ripple/portal';
import { X, Info } from 'lucide-ripple';
import button from 'styles/button.module.css';
import styles from 'styles/toast.module.css';

const toaster = createToaster(
  {
    max: 3,
    overlap: true,
    placement: 'bottom-end',
    gap: 16,
  },
);

export component MaxToasts() {
  <div>
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
      <button
        type="button"
        class={button.Root}
        onClick={() => {
          toaster.create(
            {
              title: 'New notification',
              description: 'You have a new message in your inbox.',
              type: 'info',
            },
          );
        }}
      >
        {'Add notification'}
      </button>
      <button
        type="button"
        class={button.Root}
        onClick={() => {
          const messages = [
            'John liked your post',
            'Sarah commented on your photo',
            'New follower: @designpro',
            'Your post was shared 10 times',
            'Meeting reminder in 15 minutes',
          ];
          messages.forEach((msg) => {
            toaster.create(
              {
                title: 'Notification',
                description: msg,
                type: 'info',
              },
            );
          });
        }}
      >
        {'Add 5 notifications'}
      </button>
    </div>

    <Portal>
      <Toaster {toaster}>
        component children({ toast }) {
          <Toast.Root class={styles.Root}>
            <Info class={styles.Indicator} />
            <div style={{ flex: 1 }}>
              <Toast.Title class={styles.Title}>{@toast.title}</Toast.Title>
              <Toast.Description class={styles.Description}>{@toast.description}</Toast.Description>
            </div>
            <Toast.CloseTrigger class={styles.CloseTrigger}>
              <X />
            </Toast.CloseTrigger>
          </Toast.Root>
        }
      </Toaster>
    </Portal>
  </div>
}

```

### Placement

Configure where toasts appear on the screen using the `placement` option in `createToaster`. Options include
`top-start`, `top-end`, `bottom-start`, `bottom-end`, and more.

**Example: placement**

```ripple
import { Toast, Toaster, createToaster } from 'ark-ripple/toast';
import { Portal } from 'ark-ripple/portal';
import { X } from 'lucide-ripple';
import button from 'styles/button.module.css';
import styles from 'styles/toast.module.css';

const toaster = createToaster(
  {
    placement: 'top-end',
    overlap: true,
    gap: 16,
  },
);

export component Placement() {
  <div>
    <button
      type="button"
      class={button.Root}
      onClick={() => {
        toaster.create(
          {
            title: 'Notification',
            description: 'This toast appears at the top-right corner.',
            type: 'info',
          },
        );
      }}
    >
      {'Show toast (top-end)'}
    </button>
    <Portal>
      <Toaster {toaster}>
        component children({ toast }) {
          <Toast.Root class={styles.Root}>
            <Toast.Title class={styles.Title}>{@toast.title}</Toast.Title>
            <Toast.Description class={styles.Description}>{@toast.description}</Toast.Description>
            <Toast.CloseTrigger class={styles.CloseTrigger}>
              <X />
            </Toast.CloseTrigger>
          </Toast.Root>
        }
      </Toaster>
    </Portal>
  </div>
}

```

## Guides

### Toast in Effects

When creating a toast inside effect (like `effect`, or event handlers that trigger during
render), you may encounter a "flushSync" warning. To avoid this, wrap the toast call in `queueMicrotask`:

```tsx
import { effect } from 'ripple'

export component EffectToast() {
  effect(() => {
    // ❌ This may cause flushSync warnings
    // toaster.create({ title: 'Effect triggered!' })

    // ✅ Wrap in queueMicrotask to avoid warnings
    queueMicrotask(() => {
      toaster.create({
        title: 'Effect triggered!',
        description: 'This toast was called safely from an effect',
        type: 'success',
      })
    })
  })

  <div>Component content</div>
}
```

This ensures the toast creation is deferred until after the current execution context, preventing React's concurrent
rendering warnings.

### Styling

There's a minimal styling required for the toast to work correctly.

#### Toast root

The toast root will be assigned these css properties at runtime:

- `--x` - The x position
- `--y` - The y position
- `--scale` - The scale
- `--z-index` - The z-index
- `--height` - The height
- `--opacity` - The opacity
- `--gap` - The gap between toasts

```css
[data-scope='toast'][data-part='root'] {
  translate: var(--x) var(--y);
  scale: var(--scale);
  z-index: var(--z-index);
  height: var(--height);
  opacity: var(--opacity);
  will-change: translate, opacity, scale;
  transition:
    translate 400ms,
    scale 400ms,
    opacity 400ms,
    height 400ms,
    box-shadow 200ms;
  transition-timing-function: cubic-bezier(0.21, 1.02, 0.73, 1);

  &[data-state='closed'] {
    transition:
      translate 400ms,
      scale 400ms,
      opacity 200ms;
    transition-timing-function: cubic-bezier(0.06, 0.71, 0.55, 1);
  }
}
```

#### Styling based on type

You can also style based on the `data-type` attribute.

```css
[data-scope='toast'][data-part='root'] {
  &[data-type='error'] {
    background: red;
    color: white;
  }

  &[data-type='info'] {
    background: blue;
    color: white;
  }

  &[data-type='warning'] {
    background: orange;
  }

  &[data-type='success'] {
    background: green;
    color: white;
  }
}
```

#### Mobile considerations

A very common use case is to adjust the toast width on mobile so it spans the full width of the screen.

```css
@media (max-width: 640px) {
  [data-scope='toast'][data-part='group'] {
    width: 100%;
  }

  [data-scope='toast'][data-part='root'] {
    inset-inline: 0;
    width: calc(100% - var(--gap) * 2);
  }
}
```

## API Reference

### Props

**Component API Reference**

**Root Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Root Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | toast |
| `[data-part]` | root |
| `[data-state]` | "open" | "closed" |
| `[data-type]` | The type of the item |
| `[data-placement]` | The placement of the toast |
| `[data-align]` |  |
| `[data-side]` |  |
| `[data-mounted]` | Present when mounted |
| `[data-paused]` | Present when paused |
| `[data-first]` |  |
| `[data-sibling]` |  |
| `[data-stack]` |  |
| `[data-overlap]` | Present when overlapping |


**ActionTrigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**CloseTrigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Description Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Store Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `duration` | `number` | No | The duration of the toast.
By default, it is determined by the type of the toast. |
| `gap` | `number` | No | The gap between the toasts |
| `hotkey` | `string[]` | No | The hotkey that will move focus to the toast group |
| `max` | `number` | No | The maximum number of toasts. When the number of toasts exceeds this limit, the new toasts are queued. |
| `offsets` | `string | Record<'top' | 'bottom' | 'left' | 'right', string>` | No | The offset from the safe environment edge of the viewport |
| `overlap` | `boolean` | No | Whether to overlap the toasts |
| `pauseOnPageIdle` | `boolean` | No | Whether to pause toast when the user leaves the browser tab |
| `placement` | `Placement` | No | The placement of the toast |
| `removeDelay` | `number` | No | The duration for the toast to kept alive before it is removed.
Useful for exit transitions. |


**Title Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Toaster Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `toaster` | `CreateToasterReturn` | Yes |  |
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `dir` | `'ltr' | 'rtl'` | No | The document's text/writing direction. |
| `getRootNode` | `() => Node | ShadowRoot | Document` | No | A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. |


### Context

**API:**

| Property | Type | Description |
|----------|------|-------------|
| `getCount` | `() => number` | The total number of toasts |
| `getToasts` | `() => ToastProps<any>[]` | The toasts |
| `subscribe` | `(callback: (toasts: Options<O>[]) => void) => VoidFunction` | Subscribe to the toast group |
