Toast
Short notifications with an imperative toast() API, promise tracking, an action button and stacking.
'use client';
import { Button, toast } from '@akhatmirzo/ui';
export default function ToastTypes() {
return (
<div className="flex flex-wrap items-center gap-2">
<Button variant="outline" onClick={() => toast('Havola nusxalandi')}>
Oddiy
</Button>
<Button variant="outline" onClick={() => toast.success("O'zgarishlar saqlandi")}>
success
</Button>
<Button
variant="outline"
onClick={() =>
toast.error("Ulanib bo'lmadi", {
description: "Internetni tekshirib, qayta urinib ko'ring.",
})
}
>
error
</Button>
<Button
variant="outline"
onClick={() =>
toast.warning('Obuna 3 kundan keyin tugaydi', {
description: "To'lovni oldindan qiling.",
})
}
>
warning
</Button>
<Button variant="outline" onClick={() => toast.info('Yangi versiya mavjud')}>
info
</Button>
<Button variant="outline" onClick={() => toast.loading('Hisobot tayyorlanmoqda...')}>
loading
</Button>
<Button variant="ghost" onClick={() => toast.dismiss()}>
Hammasini yopish
</Button>
</div>
);
}
Import
import { toast, Toaster } from '@akhatmirzo/ui';When to use
- To report the result of an action — "Saved", "Link copied", "Payment received". It doesn't interrupt the user and goes away on its own.
- Undo — after a delete, offer a "Restore" button through
action. This is often friendlier thanconfirm(), because you don't stop the user with a question. - Progress of a longer task — with
toast.promise()ortoast.loading()+{ id }, one toast moves from "loading" to the result. - A question that needs an answer calls for
confirm(), not a toast. A field validation error belongs next to the field — in theerrorprop ofField.
Toaster setup
toast() writes to a module-level store; <Toaster /> renders it. Your app needs exactly one
<Toaster /> — without it, toast() doesn't throw, but nothing shows up.
'use client';
import { Toaster } from '@akhatmirzo/ui';
import type { ReactNode } from 'react';
export function Providers({ children }: { children: ReactNode }) {
return (
<>
{children}
<Toaster position="bottom-right" />
</>
);
}After that, call toast() from anywhere — a component, an event handler, or even a plain
function outside React (say, the error handler of your API client).
Types
| Function | Icon | Default duration |
|---|---|---|
toast() | none | 4 s |
toast.success() | green ✓ | 4 s |
toast.error() | red ! | 6 s |
toast.warning() | yellow triangle | 4 s |
toast.info() | blue i | 4 s |
toast.loading() | spinner | until updated |
Each function returns the toast id. toast.dismiss(id) closes one toast; toast.dismiss()
closes them all.
Description and action
description adds a second line. action adds a small button on the right: { label, onClick }.
Clicking it closes the toast; to keep it open, call event.preventDefault() inside onClick.
'use client';
import { Button, toast } from '@akhatmirzo/ui';
export default function ToastAction() {
return (
<div className="flex flex-wrap items-center gap-2">
<Button
variant="outline"
onClick={() =>
toast.success('Buyurtma yaratildi', {
description: "#1042 · 1 250 000 so'm",
action: { label: "Ko'rish", onClick: () => toast.info('Buyurtma #1042 ochildi') },
})
}
>
Amal bilan
</Button>
<Button
variant="outline"
onClick={() =>
toast("Mahsulot o'chirildi", {
description: 'iPhone 15 Pro, 256 GB',
action: {
label: 'Qaytarish',
onClick: () => toast.success('Mahsulot qaytarildi'),
},
})
}
>
Bekor qilish (undo)
</Button>
</div>
);
}
toast('Product deleted', {
description: 'iPhone 15 Pro, 256 GB',
action: { label: 'Restore', onClick: () => restoreProduct(id) },
});toast.promise()
Tracks a promise: loading first, then success or error — all in a single toast. Pass
functions for success and error to receive the result or the error. You can also pass a
function that returns the promise. toast.promise() returns the original promise, so you can
await it or attach .catch().
'use client';
import { Button, toast } from '@akhatmirzo/ui';
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
async function saveOrder() {
await wait(1500);
return 1042;
}
async function failingRequest(): Promise<void> {
await wait(1200);
throw new Error('Server javob bermadi');
}
export default function ToastPromise() {
return (
<div className="flex flex-wrap items-center gap-2">
<Button
variant="outline"
onClick={() =>
toast.promise(saveOrder, {
loading: 'Saqlanmoqda...',
success: (id) => `Buyurtma #${id} saqlandi`,
error: 'Xatolik yuz berdi',
})
}
>
Muvaffaqiyatli
</Button>
<Button
variant="outline"
onClick={() =>
toast
.promise(failingRequest, {
loading: 'Yuborilmoqda...',
success: 'Yuborildi',
error: (e) => `Xatolik: ${(e as Error).message}`,
})
.catch(() => {})
}
>
Xato bilan
</Button>
</div>
);
}
const order = await toast.promise(api.saveOrder(data), {
loading: 'Saving...',
success: (o) => `Order #${o.id} saved`,
error: (e) => `Error: ${(e as Error).message}`,
});Updating by id
With { id }, the toast with that id is updated (or created with that id if it doesn't exist).
You can change its type, title and description; the timer starts over.
'use client';
import { Button, toast } from '@akhatmirzo/ui';
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export default function ToastUpdate() {
const upload = async () => {
const id = toast.loading('Fayl yuklanmoqda...', { description: 'hisobot.xlsx' });
await wait(1000);
toast.loading('Fayl tekshirilmoqda...', { id, description: 'hisobot.xlsx' });
await wait(1000);
toast.success('Fayl yuklandi', { id, description: 'hisobot.xlsx · 2,4 MB' });
};
return (
<Button variant="outline" onClick={upload}>
Faylni yuklash
</Button>
);
}
Stacking
At most 3 toasts are visible at a time (visibleToasts); the rest wait in line with their timers
paused. Visible toasts sit in a compact stack that fans out on hover or focus, and all timers
pause meanwhile. Swipe a toast sideways to dismiss it.
'use client';
import { Button, toast } from '@akhatmirzo/ui';
export default function ToastStack() {
const burst = () => {
toast.info("Yangi mijoz ro'yxatdan o'tdi");
toast.success("To'lov qabul qilindi", { description: "350 000 so'm" });
toast.warning('Omborda 3 ta qoldi');
toast('Yangi sharh qoldirildi');
};
return (
<div className="flex flex-wrap items-center gap-2">
<Button variant="outline" onClick={burst}>
4 tasini birdan
</Button>
<Button variant="ghost" onClick={() => toast.dismiss()}>
Hammasini yopish
</Button>
</div>
);
}
Props
toast(title, options?)
title is a ReactNode. Every variant (toast.success ...) takes the same options except
type.
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | number | — | Updates the toast with this id, or creates one with it |
type | 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading' | 'default' | toast() only; the variants set it from their name |
description | ReactNode | — | Second line |
action | { label: ReactNode; onClick?: (event) => void; altText?: string } | — | Button on the right. altText — for screen readers when label isn't text |
duration | number | 4000 | ms. error — 6000, loading — Infinity |
icon | ReactNode | — | Replaces the status icon; null hides it |
onDismiss | (id: ToastId) => void | — | Called whenever the toast closes, for any reason |
toast.promise(promise, messages, options?)
| Prop | Type | Default | Description |
|---|---|---|---|
promiserequired | Promise<T> | (() => Promise<T>) | — | The promise to track, or a function that returns it |
messages.loadingrequired | ReactNode | — | Pending text |
messages.successrequired | ReactNode | ((data: T) => ReactNode) | — | Success text |
messages.errorrequired | ReactNode | ((error: unknown) => ReactNode) | — | Error text |
options | Omit<ToastOptions, 'type' | 'duration'> | — | description, action, icon ... (applied at every stage) |
Returns the original Promise<T>.
Toaster
| Prop | Type | Default | Description |
|---|---|---|---|
position | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'bottom-right' | On phones (< 640px), always at the bottom and full width |
duration | number | 4000 | Default duration, ms (error — 6000, loading — infinite) |
visibleToasts | number | 3 | How many toasts are visible at once |
gap | number | 8 | Gap between toasts when the stack is expanded, px |
closeButton | boolean | false | A ✕ button on every toast |
hotkey | string[] | ['F8'] | Key that moves focus to the toast region |
labels | { region?: string; toast?: string; close?: string } | — | Screen reader texts (see below) |
className | string | — | Class for the toast region (viewport) |
Default labels are Uzbek: region — 'Bildirishnomalar ({hotkey})' ({hotkey} is replaced
with the key name), toast — 'Bildirishnoma' (the word announced before each toast), close —
'Yopish'. For an English app:
<Toaster labels={{ region: 'Notifications ({hotkey})', toast: 'Notification', close: 'Close' }} />Data attributes
| Attribute | On | Value |
|---|---|---|
data-type | toast | default · success · error · warning · info · loading |
data-index | toast | position in the stack (0 is the front) |
data-front | toast | present on the front toast |
data-expanded | toast, region | present while the stack is expanded |
data-position | region | the position value |
Keyboard
| Key | Action |
|---|---|
F8 | Moves focus to the toast region (hotkey) |
Tab | Moves between toasts and their buttons |
Esc | Closes the focused toast |
Enter / Space | Presses the action or ✕ button |
Accessibility
- Announcements. Toasts render with
role="status", so screen readers announce them without interrupting. Theerrortype usesrole="alert"and is announced right away. - Timers pause while the pointer is over the toasts or focus is inside the region, so users have time to read.
- Reachable by keyboard.
F8(or yourhotkey) moves focus to the toast region, which is labeled "Bildirishnomalar (F8)" by default. - Action buttons. If
labelisn't text (an icon, say), providealtTextfor screen readers. - Queued toasts are
inert, so focus never lands on them. - Reduced motion. With
prefers-reduced-motion, the slide becomes a fade.