Toast
Display brief notification messages with support for variants, actions, and promises
Toast
Display brief, non-intrusive notification messages to provide feedback on user actions. Built on Sonner, featuring automatic stacking, promise handling, and rich customization options.
Installation
import { toast } from '@geckoui/geckoui';Setup
Toast is enabled automatically when GeckoUIProvider is present in your app. See GeckoUIProvider for setup instructions.
// app/layout.tsx or _app.tsx
import { GeckoUIProvider } from '@geckoui/geckoui';
export default function RootLayout({ children }) {
return (
<html>
<body>
<GeckoUIProvider>
{children}
</GeckoUIProvider>
</body>
</html>
);
}Basic Usage
'use client';
import { toast, Button } from '@geckoui/geckoui';
function Example() {
return (
<Button onClick={() => toast('This is a basic toast notification')}>
Show Toast
</Button>
);
}Toast API
toast(message, options?)
Display a default toast notification.
toast('Hello World');
toast('Hello World', { duration: 5000 });Variant Methods
| Method | Description |
|---|---|
toast.success(message, options?) | Success notification (green) |
toast.error(message, options?) | Error notification (red) |
toast.warning(message, options?) | Warning notification (yellow) |
toast.info(message, options?) | Info notification (blue) |
toast.loading(message, options?) | Loading state with spinner |
toast.promise(promise, messages) | Automatic promise state handling |
Control Methods
| Method | Description |
|---|---|
toast.dismiss(id?) | Dismiss a specific toast or all toasts |
toast.custom(component, options?) | Display a custom component |
Options
| Option | Type | Default | Description |
|---|---|---|---|
description | string | ReactNode | - | Additional description below the message |
duration | number | 4000 | Duration in ms (use Infinity for persistent) |
position | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'bottom-right' | Toast position on screen |
action | { label: string; onClick: () => void } | - | Action button |
cancel | { label: string; onClick: () => void } | - | Cancel button |
id | string | number | - | Custom toast ID |
onDismiss | () => void | - | Callback when dismissed |
onAutoClose | () => void | - | Callback when the duration elapses |
icon | ReactNode | null | - | Replace the variant icon. null removes it |
className | string | - | Custom CSS class |
style | CSSProperties | - | Inline styles |
closeButton | boolean | false | Show close button |
dismissible | boolean | true | Whether it can be swiped away |
Dismissing
Every toast can be swiped away, whatever is in it — a custom one as much as a plain one.
A toast leaves by the edges it sits near, so a bottom-right one goes right or down, and a top-left one goes up or left. A centred toast has only the one way out, since either side would be across the screen rather than off it. Dragging it back inwards does nothing.
Either a long drag or a quick flick will do it, so neither has to be done carefully.
// this one has to be answered rather than waved off
toast.warning('Your session is about to end', {
duration: Infinity,
dismissible: false,
closeButton: true,
action: { label: 'Stay signed in', onClick: renew }
});Buttons inside a toast still work: a press that starts on one is never taken as the start of a drag.
Swiping is pointer only. A toast that neither closes itself nor can be swiped leaves a keyboard user no way to be rid of it, so give that combination a closeButton — nothing turns one on for you.
Examples
Toast Variants
<Button onClick={() => toast.success('Operation completed successfully!')}>
Success
</Button>
<Button onClick={() => toast.error('An error occurred!')}>
Error
</Button>
<Button onClick={() => toast.warning('This is a warning message')}>
Warning
</Button>
<Button onClick={() => toast.info('Here is some information')}>
Info
</Button>With Description
toast.success('Account created', {
description: 'Your account has been created successfully. You can now sign in.'
});
toast.error('Upload failed', {
description: 'The file size exceeds the 10MB limit. Please choose a smaller file.'
});With Action Button
toast('Email sent', {
description: 'Your email has been sent to john@example.com',
action: {
label: 'Undo',
onClick: () => toast.info('Email sending cancelled')
}
});Promise Toast
Automatically handles loading, success, and error states:
'use client';
import { toast, Button } from '@geckoui/geckoui';
function Example() {
const downloadReport = async () => {
const response = await fetch('/api/report');
if (!response.ok) throw new Error('Download failed');
return response.json();
};
return (
<Button
onClick={() =>
toast.promise(downloadReport(), {
loading: 'Downloading report...',
success: (data) => `${data.name} downloaded successfully`,
error: 'Download failed. Please try again.'
})
}
>
Download Report
</Button>
);
}Custom Duration
// Quick message (1 second)
toast('Quick message', { duration: 1000 });
// Normal message (4 seconds - default)
toast('Normal message', { duration: 4000 });
// Persistent message (stays until dismissed)
toast('Persistent message', { duration: Infinity });Loading Toast
function Example() {
const handleProcess = () => {
const toastId = toast.loading('Processing your request...');
// Simulate async operation
setTimeout(() => {
toast.success('Process completed!', { id: toastId });
}, 3000);
};
return <Button onClick={handleProcess}>Start Process</Button>;
}Dismissing a promise toast
Dismissing the loading toast cancels the follow-up, so the success or error toast does not appear over your dismissal:
toast.promise(upload(), { loading: 'Uploading…', success: 'Uploaded', error: 'Failed' });
// user dismisses the loading toast -> nothing appears when the upload settlesReusing the id yourself still shows the toast, because that is an explicit request rather than the library finishing its own lifecycle:
const id = toast.loading('Uploading…');
// even if the user dismissed it, this shows:
toast.success('Uploaded', { id });Dismiss Toasts
// Dismiss a specific toast
const id = toast('This toast can be dismissed manually');
toast.dismiss(id);
// Dismiss all toasts
toast.dismiss();
// Auto-dismiss after delay
const toastId = toast('Auto-dismissing...', { duration: Infinity });
setTimeout(() => toast.dismiss(toastId), 3000);Custom Styling
toast('Custom styled toast', {
className: 'border-2 border-blue-500',
style: {
background: 'var(--color-surface-secondary)'
}
});Rich Content
toast(
<div>
<strong className="block text-sm font-semibold">New Feature Available</strong>
<p className="text-sm mt-1">
Check out our new dashboard analytics with real-time data visualization.
</p>
</div>,
{ duration: 5000 }
);Toast Positions
toast('Top Left', { position: 'top-left' });
toast('Top Center', { position: 'top-center' });
toast('Top Right', { position: 'top-right' });
toast('Bottom Left', { position: 'bottom-left' });
toast('Bottom Center', { position: 'bottom-center' });
toast('Bottom Right', { position: 'bottom-right' });Global Configuration
Configure toast defaults via the toastOptions prop on GeckoUIProvider:
<GeckoUIProvider
toastOptions={{
position: 'top-right',
duration: 3000,
closeButton: true,
visibleToasts: 5,
toastClassName: 'my-toast'
}}
>
<App />
</GeckoUIProvider>Available Global Options
| Option | Type | Default | Description |
|---|---|---|---|
position | ToastPosition | 'bottom-right' | Where stacks appear |
duration | number | 4000 | Default duration in ms |
closeButton | boolean | false | Show the dismiss button on every toast |
visibleToasts | number | 3 | How many toasts render per position at once |
dismissible | boolean | true | Whether toasts can be swiped away |
gap | number | 14 | Pixels between stacked toasts |
offset | number | 24 | Pixels between the stack and the viewport edge |
className | string | - | Class on the stack element for each position |
toastClassName | string | - | Class applied to every toast |
toastStyle | CSSProperties | - | Style applied to every toast |
iconClassName | string | - | Class on the variant icon |
messageClassName | string | - | Class on the message line |
descriptionClassName | string | - | Class on the description line |
actionClassName | string | - | Class on the action button |
cancelClassName | string | - | Class on the cancel button |
closeClassName | string | - | Class on the dismiss button |
There is no theme option: toasts read the same variables as the rest of the library, so they
follow your theme automatically.
Styling with CSS
Every value is a custom property, so you can restyle the whole toast system from your own stylesheet without overriding a single class:
:root {
--gecko-toast-width: 28rem;
--gecko-toast-offset: 40px;
--gecko-toast-radius: 1.5rem;
--gecko-toast-padding: 1.25rem;
--gecko-toast-shadow: 0 20px 40px -12px rgb(0 0 0 / 0.35);
--gecko-toast-duration: 320ms;
}| Variable | Default | Applies to |
|---|---|---|
--gecko-toast-width | min(400px, 100vw - 2rem) | Stack width |
--gecko-toast-offset | 24px | Distance from the viewport edge |
--gecko-toast-z | 3000 | Stack z-index |
--gecko-toast-bg | --color-surface-primary | Toast background |
--gecko-toast-fg | --color-text-primary | Message colour |
--gecko-toast-muted | --color-text-muted | Description colour |
--gecko-toast-border | --color-border-primary | Border colour |
--gecko-toast-accent | per variant | Icon, spinner and action button |
--gecko-toast-radius | 0.5rem | Corner radius |
--gecko-toast-padding | 1rem | Inner padding |
--gecko-toast-gap | 0.75rem | Gap between icon and body |
--gecko-toast-shadow | soft drop shadow | Toast shadow |
--gecko-toast-duration | 200ms | Enter and exit transition |
Variant colours come from the shared semantic tokens, so changing them updates every component that reports status:
:root {
--color-success: oklch(0.65 0.18 150);
--color-error: oklch(0.6 0.22 25);
--color-warning: oklch(0.7 0.16 60);
--color-info: oklch(0.62 0.19 260);
}Scope a variable to one variant when you only want to change that one:
.GeckoUIToast[data-variant="error"] {
--gecko-toast-bg: var(--color-error);
--gecko-toast-fg: white;
--gecko-toast-accent: white;
}Class names
GeckoUIToaster- The stack, withdata-positionGeckoUIToaster__item- Wrapper around each toastGeckoUIToast- The toast, withdata-variantanddata-stateGeckoUIToast__icon,__body,__message,__descriptionGeckoUIToast__actions,__action,__cancel,__closeGeckoUIToast__custom- Wrapper fortoast.customcontent
Accessibility
- Toasts are announced by screen readers
- Automatically removed after duration expires
- Action buttons are keyboard accessible
- Supports
prefers-reduced-motionfor animations - ARIA live regions for dynamic content
Best Practices
- Keep messages brief: Toast messages should be short and scannable
- Use appropriate variants: Match the toast type to the message (success, error, etc.)
- Provide actions when needed: Allow users to undo or retry failed operations
- Don't overuse: Avoid showing too many toasts at once
- Set appropriate durations:
- Success: 2-4 seconds
- Error: 5-7 seconds (or with action)
- Loading: Until operation completes
- Position wisely: Bottom-right is least intrusive for most layouts
- Avoid critical information: Don't use toasts for important information that users must read