Gecko UIGecko UI

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

MethodDescription
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

MethodDescription
toast.dismiss(id?)Dismiss a specific toast or all toasts
toast.custom(component, options?)Display a custom component

Options

OptionTypeDefaultDescription
descriptionstring | ReactNode-Additional description below the message
durationnumber4000Duration 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
idstring | number-Custom toast ID
onDismiss() => void-Callback when dismissed
onAutoClose() => void-Callback when the duration elapses
iconReactNode | null-Replace the variant icon. null removes it
classNamestring-Custom CSS class
styleCSSProperties-Inline styles
closeButtonbooleanfalseShow close button
dismissiblebooleantrueWhether 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 settles

Reusing 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

OptionTypeDefaultDescription
positionToastPosition'bottom-right'Where stacks appear
durationnumber4000Default duration in ms
closeButtonbooleanfalseShow the dismiss button on every toast
visibleToastsnumber3How many toasts render per position at once
dismissiblebooleantrueWhether toasts can be swiped away
gapnumber14Pixels between stacked toasts
offsetnumber24Pixels between the stack and the viewport edge
classNamestring-Class on the stack element for each position
toastClassNamestring-Class applied to every toast
toastStyleCSSProperties-Style applied to every toast
iconClassNamestring-Class on the variant icon
messageClassNamestring-Class on the message line
descriptionClassNamestring-Class on the description line
actionClassNamestring-Class on the action button
cancelClassNamestring-Class on the cancel button
closeClassNamestring-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;
}
VariableDefaultApplies to
--gecko-toast-widthmin(400px, 100vw - 2rem)Stack width
--gecko-toast-offset24pxDistance from the viewport edge
--gecko-toast-z3000Stack z-index
--gecko-toast-bg--color-surface-primaryToast background
--gecko-toast-fg--color-text-primaryMessage colour
--gecko-toast-muted--color-text-mutedDescription colour
--gecko-toast-border--color-border-primaryBorder colour
--gecko-toast-accentper variantIcon, spinner and action button
--gecko-toast-radius0.5remCorner radius
--gecko-toast-padding1remInner padding
--gecko-toast-gap0.75remGap between icon and body
--gecko-toast-shadowsoft drop shadowToast shadow
--gecko-toast-duration200msEnter 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, with data-position
  • GeckoUIToaster__item - Wrapper around each toast
  • GeckoUIToast - The toast, with data-variant and data-state
  • GeckoUIToast__icon, __body, __message, __description
  • GeckoUIToast__actions, __action, __cancel, __close
  • GeckoUIToast__custom - Wrapper for toast.custom content

Accessibility

  • Toasts are announced by screen readers
  • Automatically removed after duration expires
  • Action buttons are keyboard accessible
  • Supports prefers-reduced-motion for animations
  • ARIA live regions for dynamic content

Best Practices

  1. Keep messages brief: Toast messages should be short and scannable
  2. Use appropriate variants: Match the toast type to the message (success, error, etc.)
  3. Provide actions when needed: Allow users to undo or retry failed operations
  4. Don't overuse: Avoid showing too many toasts at once
  5. Set appropriate durations:
    • Success: 2-4 seconds
    • Error: 5-7 seconds (or with action)
    • Loading: Until operation completes
  6. Position wisely: Bottom-right is least intrusive for most layouts
  7. Avoid critical information: Don't use toasts for important information that users must read
  • Alert - Display prominent alerts in content
  • Dialog - Modal dialogs for important messages

Resources