# Migration

## From Vyrn v4

v5 is a rewrite of the internals, but the v4 public API still works. Every
legacy prop and option below is still accepted.

### Breaking changes

| Change | Why | What to do |
| --- | --- | --- |
| **React 17 dropped** (peer is now `>=18`) | v5 uses features that need React 18. | Upgrade React, or stay on `vyrn@4`. |
| **`position` default is now `'bottom-right'`** | v4's code defaulted to `'top-right'` while the docs claimed `'bottom-center'`. Neither matched; `'bottom-right'` matches Sonner. | Set `position` explicitly if you relied on the old default. |
| **`duration` default is now `4000`** (was `3000`) | Matches Sonner; 3 s is short for a toast with a description. | Pass `duration={3000}` to keep the old feel. |
| **Overflow toasts queue instead of being deleted** | v4 silently discarded toasts past `maxToasts`, and the id it returned was already dead. | Nothing — this is a bug fix. Expect to see toasts you previously lost. |
| **`swipeDirection` derived from `position` by default** | A single global direction was wrong for half the corners. | Still accepted; prefer `swipeDirections={[...]}`. |
| **Toast DOM structure changed** | `<div>` soup became a semantic `<ol>`/`<li>`, and styling hooks moved to `data-*`. | Restyle against `data-type`, `data-front`, … instead of `.vyrn-toast-success`. |
| **`progress` no longer drives dismissal** | The countdown is CSS now. `progress` means *determinate* progress. | Use `duration` for timing, `progress` for a measured bar. |
| **`ToastProps.content` may be `undefined`** | `title` is accepted as a Sonner-style alias. | Read `content ?? title` if you inspect records directly. |

### Props that used to do nothing

These were declared and documented in v4 but were never read by the
implementation. They now work:

`theme`, `expand`, `gap`, `offset`, `toastOptions`, `closeButton`, `invert`,
`groupId`.

`swipeClose` was removed — it never did anything. Use `dismissible: false` to
block dismissal, or `swipeDirections: []` to disable swiping.

### Still supported (legacy aliases)

| Legacy | Preferred |
| --- | --- |
| `<ToastProvider>` | `<Toaster />` |
| `defaultPosition` | `position` |
| `defaultLayout` | `layout` |
| `defaultDuration` | `duration` |
| `maxToasts` | `visibleToasts` |
| `showCloseButton` | `closeButton` |
| `containerClassName` | `className` |
| `swipeDirection` | `swipeDirections` |
| `customStyles` | `style` |
| `toast.clearAll()` | `toast.dismiss()` |
| `onClose` | `onDismiss` / `onAutoClose` |
| `useToast()` | `toast` + `useVyrn()` |

`useToast()` no longer requires a surrounding provider, and now also returns the
live `toasts` array.

### Things that were broken and now aren't

- Toasts auto-dismiss in background tabs. v4's `requestAnimationFrame` loop was paused by the browser, so toasts accumulated indefinitely and then all vanished at once on return.
- `layout="stack"` actually staggers the toasts. In v4 every toast rendered at the identical transform, so only the front one was ever visible.
- Stack offsets use measured heights, so multi-line toasts no longer overlap.
- `toast()` before `<Toaster />` mounts queues instead of throwing.
- Unmounting one of two `<Toaster />`s no longer breaks `toast()`.
- `toast.dismiss()` with no argument dismisses everything (it was a silent no-op).
- Hovering one toast pauses all of them.
- Swipe works on toasts that have buttons.
- The invalid `role="text"` and the nested `aria-live` regions are gone.

---

## From Sonner

Change the import and it works:

```diff
- import { Toaster, toast } from 'sonner';
+ import { Toaster, toast } from 'vyrn';
```

### Same

`toast()`, `.success`, `.error`, `.info`, `.warning`, `.message`, `.loading`,
`.custom` (node or `(id) => node`), `.promise`, `.dismiss(id?)`, `.getToasts()`,
`.getHistory()`,
`useSonner()`; options `id`, `description`, `duration`, `icon`, `action`,
`cancel`, `position`, `dismissible`, `important`, `className`,
`descriptionClassName`, `classNames`, `style`, `unstyled`, `invert`,
`richColors`, `closeButton`, `onDismiss`, `onAutoClose`, `actionButtonStyle`,
`cancelButtonStyle`; and `<Toaster>` props `position`, `theme`, `dir`,
`richColors`, `expand`, `duration`, `visibleToasts`, `closeButton`, `offset`,
`mobileOffset`, `gap`, `toastOptions`, `icons`, `loadingIcon`, `hotkey`,
`swipeDirections`, `pauseWhenPageIsHidden`, `containerAriaLabel`, and `offset` /
`mobileOffset` in both scalar and `{ top, right, bottom, left }` form.

### Different

| | Sonner | Vyrn |
| --- | --- | --- |
| `closeButton` default | `false` | `true` (v4 compatibility) — pass `closeButton={false}` |
| `visibleToasts` default | `3` | `5` |
| Countdown bar | none | on by default — `showProgressBar={false}` to remove |
| `toast.promise` return | thenable with `unwrap` | `{ id, unwrap() }` — use `.unwrap()`, or `.id` for the id |
| Two `<Toaster>`s mounted | both render every toast | only the first paints; no duplicates |
| CSS selectors | `[data-sonner-toast]` | `[data-vyrn-toast]`, class `.vyrn-toast` |
| CSS variables | `--normal-bg`, … | `--vyrn-bg`, … |

### Extra in Vyrn

`layout="stack"`, `size`, `color`, `swipeThreshold`, `zIndex`,
`showProgressBar`, `closeButtonAriaLabel`, `pauseOnFocusLoss`, `newestFirst`,
`closeOnClick`, per-type `toastOptions.types`; and per toast: `actions[]`
(several buttons), `input`, `expandable`, `priority`, `progress`, `groupId`,
`preventDuplicate`, `closeOnClick`, `soundEffect`, `jsx`, `customComponent`,
`onClick`; plus `toast.isActive(id)`.

Features borrowed from elsewhere in the ecosystem: `pauseOnFocusLoss` and
`closeOnClick` (react-toastify), per-type `toastOptions` (react-hot-toast),
`newestFirst` (react-toastify's `newestOnTop`), `preventDuplicate` (notistack).

Vyrn also guards against things Sonner leaves to you: consumer callbacks that
throw are isolated, out-of-range `progress` is clamped, negative durations are
treated as persistent, swipes use pointer capture, focus is restored when the
focused toast is dismissed, and server output is toast-free so hydration can
never mismatch.
