Toast

Generates toast notifications.

Installation

pnpm dlx shadcn@latest add @lumi-ui/toast
Render the Toaster component once in your root layout.
app/layout.tsx
export default function RootLayout({
 children,
}: Readonly<{
 children: React.ReactNode;
}>) {
 return (  
   <html>
     <body>
       <div className="root">
         {children}
         <Toaster
           position="bottom-right"
           swipeDirection={["right", "down"]}
           limit={3}
         />
       </div>
     </body>
   </html>
 );
}
Add global CSS variables to the @theme block of your global CSS file.
globals.css
  /* Spacing around window */
  --toast-viewport-padding: 2rem;
  /* Toast width */
  --toast-width: 22rem;
  /* Spacing between toasts */
  --toast-gap: 0.75rem;
  /* Spacing between stacked toast */
  --toast-peek: 0.75rem;
  /* Mobile vs Desktop Widths */
  --toast-max-width-mobile: 80%; 
  --toast-max-width-desktop: var(--toast-width);
Paste the following code into your global CSS file. Which handles both the viewport and toast styles and animations.
globals.css
/* 
   Toast Viewport
*/
@layer utilities {
  .toast-viewport {
    @apply fixed flex flex-col w-[var(--toast-max-width-mobile)] lg:w-[var(--toast-max-width-desktop)] outline-none pointer-events-none;
  }
  /* Viewport Positioning */
  .toast-viewport[data-position^="top"] { top: var(--toast-viewport-padding); bottom: auto; }
  .toast-viewport[data-position^="bottom"] { bottom: var(--toast-viewport-padding); top: auto; }
  .toast-viewport[data-position$="left"] { left: var(--toast-viewport-padding); right: auto; }
  .toast-viewport[data-position$="right"] { right: var(--toast-viewport-padding); left: auto; }
  .toast-viewport[data-position$="center"] { 
    @apply left-1/2 right-auto -translate-x-1/2;
  }
}
 
/* 
   Toast Root
*/
@layer utilities {
  .toast-root {
    @apply absolute w-full pointer-events-auto;
    /* Stacking */
    --toast-index: 0; /* Default fallback */
    z-index: calc(1000 - var(--toast-index));
    
    /* Transitions */
    transition: 
      transform 0.5s cubic-bezier(0.22, 1, 0.36, 1), 
      opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1), 
      height 0.15s ease-out;
    
    /* Height Calculation */
    height: var(--toast-frontmost-height, var(--toast-height));
    
    /* Common Ghost Element (for hit areas) */
    &::after {
      content: "";
      position: absolute;
      left: 0;
      width: 100%;
      height: calc(var(--toast-gap) + 1px);
      background: transparent;
    }
    
    /* Math for collapsed stacking (shrinking effect) */
    --toast-scale: calc(max(0, 1 - (var(--toast-index) * 0.1)));
    --toast-shrink: calc(1 - var(--toast-scale));
    --toast-height-approx: var(--toast-frontmost-height, var(--toast-height));
  }
  /* 
    Positions
  */
  /* Bottom */
  .toast-root[data-position^="bottom"] {
    @apply bottom-0 top-auto;
    transform-origin: bottom center;
 
    /* Direction Multipliers: Bottom is "Positive" direction for entering from below */
    --dir-y: 1; 
    
    /* Calculate Offsets */
    --collapsed-offset-y: calc(
      (var(--toast-index) * var(--toast-peek) * -1) - /* Stack peek upwards */
      (var(--toast-shrink) * var(--toast-height-approx)) /* Shrink upwards */
    );
    
    --expanded-offset-y: calc(
      (var(--toast-offset-y) * -1) - /* Move up based on total stack height */
      (var(--toast-index) * var(--toast-gap))
    );
 
    /* Default Transform: Collapsed + Swipe Handling */
    transform: 
      translateX(var(--toast-swipe-movement-x, 0px)) 
      translateY(calc(var(--toast-swipe-movement-y, 0px) + var(--collapsed-offset-y))) 
      scale(var(--toast-scale));
  }
 
  /* Top */
  .toast-root[data-position^="top"] {
    @apply top-0 bottom-auto;
    transform-origin: top center;
 
    /* Direction Multipliers: Top is "Negative" direction for entering from above */
    --dir-y: -1;
 
    /* Calculate Offsets (Signs flipped from Bottom) */
    --collapsed-offset-y: calc(
      (var(--toast-index) * var(--toast-peek)) + /* Stack peek downwards */
      (var(--toast-shrink) * var(--toast-height-approx)) /* Shrink downwards */
    );
    
    --expanded-offset-y: calc(
      var(--toast-offset-y) + 
      (var(--toast-index) * var(--toast-gap))
    );
 
    /* Default Transform: Collapsed + Swipe Handling */
    transform: 
      translateX(var(--toast-swipe-movement-x, 0px)) 
      translateY(calc(var(--toast-swipe-movement-y, 0px) + var(--collapsed-offset-y))) 
      scale(var(--toast-scale));
  }
  
  /* 
    State Modifiers
  */
  /* Expanded State */
  .toast-root[data-expanded] {
    height: var(--toast-height);
    transform: 
      translateX(var(--toast-swipe-movement-x, 0px)) 
      translateY(var(--expanded-offset-y)) 
      scale(1);
  }
  /* Entering Animation */
  .toast-root[data-starting-style] {
    opacity: 0;
    transform: translateY(calc(var(--dir-y) * 150%));
  }
  /* Exiting Animation */
  .toast-root[data-ending-style] {
    opacity: 0;
    /* Default Exit */
    &:not([data-swipe-direction]) {
      transform: translateY(calc(var(--dir-y) * 150%));
    }
    /* Swipe Up/Down */
    &[data-swipe-direction="up"] {
      transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));
    }
    &[data-swipe-direction="down"] {
      transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));
    }
    /* Swipe Left/Right */
    &[data-swipe-direction="left"] {
      transform: 
        translateX(calc(var(--toast-swipe-movement-x) - 150%)) 
        translateY(var(--expanded-offset-y));
    }
    &[data-swipe-direction="right"] {
      transform: 
        translateX(calc(var(--toast-swipe-movement-x) + 150%)) 
        translateY(var(--expanded-offset-y));
    }
  }
  /* Limited State */
  .toast-root[data-limited] {
    opacity: 0;
    transform: scale(0.9);
  }
}
Use the toast object to trigger notifications from anywhere in your app.
import { toast } from "@/components/ui/toast"
 
toast.add({ title: "Event Created", description: "Monday at 9:00am" })
 
// Semantic Variants
toast.success({ title: "Success", description: "Your event has been created" })
toast.error({ title: "Error occurred", description: "Please try again" })

Features

Toast variants

Closable

Promise

Varying heights

Cookbook

Undo

Anchored toasts

Updating toasts

Custom content

Custom anchored toasts

Customization

Since you own the code, you can tweak the internal logic to fit your design system.

Styling (Variants)

We use cva to handle variants. Modify the toastVariants definition to change colors or borders.

toast.tsx
const toastVariants = cva(
  "rounded-sm outline ...", // Base styles
  {
    variants: {
      type: {
        // Add new types or modify existing color schemes here
        purple: "bg-purple-50 text-purple-900 border-purple-200", 
      }
    }
  }
)

Icons

The icons are mapped in the Icons constant. You can swap these for a different icon library or custom SVGs.

toast.tsx
const Icons = {
  success: CheckCircle2, // Replace with your own icon
  error: AlertCircle,
  // ...
};

Layout Structure

If you want to change the layout (e.g., move the action button below the text instead of to the right), modify the JSX inside StackedToast -> ToastContent.

toast.tsx
<div className="flex flex-col gap-2"> {/* Changed to column */}
   <ToastTitle>{toast.title}</ToastTitle>
   {/* ... */}
</div>

Anchored Toasts

The toast.anchor() method is useful for "Copied!" tooltips or context-specific errors. It uses a separate BaseToast.Provider internally. You can customize the AnchoredToast component if you need different layout or use toast.anchor to create a unique design that fits your use case.

Animation

Animations are handled via CSS/Tailwind classes. By default, we support all toast positions and directions of swipe dismiss. Feel free to modify the CSS to fit your design system.

toast.tsx
<BaseToast.Root
  data-slot="toast-root"
  toast={toast}
  className={cn(
    !isCustomContent && toastVariants({ type: "default" }),
    "data-[starting-style]:opacity-0 data-[starting-style]:scale-95 ",
    "data-[ending-style]:opacity-0 data-[ending-style]:scale-95 ",
    "transition-all duration-200"
  )}
>

API Reference

Toaster

The provider component that renders the viewport.

PropTypeDefaultDescription
positionToastPosition"bottom-right"Where the stack appears. Options: top-left, top-center, top-right, bottom-left, bottom-center, bottom-right.
swipeDirectionSwipeDirection["down", "right"]Direction users can swipe to dismiss.
limitnumber3Maximum number of toasts visible at once.

toast

The main interface for managing toasts. Extends Base UI's useToastManager.

MethodArgumentsDescription
successoptionspreset type: "success"
erroroptionspreset type: "error"
warningoptionspreset type: "warning"
infooptionspreset type: "info"
anchorelement, optionsSpawns a toast physically attached to a DOM element (popover style).

options

Extends Base UI's Method options with custom data fields.

PropertyTypeDescription
closablebooleanIf true, renders a close button.
customContentReactNodeOverrides the default internal structure of the toast (including the arrow if it's AnchoredToast).