Tiesen LogoYuki UI
Components

Toast

A collection of toast notification components

Component Source

Installation

CLI

npx shadcn add https://yuki-ui.vercel.app/r/toast.json

Manual

Copy and paste the following code into your project.

'use client'import { Toast as ToastPrimitive } from '@base-ui/react'import {  CircleCheckIcon,  InfoIcon,  Loader2Icon,  OctagonXIcon,  TriangleAlertIcon,} from 'lucide-react'import { buttonVariants } from '@/components/ui/button'import { cn } from '@/lib/utils'const toast = ToastPrimitive.createToastManager()const anchoredToast = ToastPrimitive.createToastManager()type ToastPosition =  | 'top-left'  | 'top-center'  | 'top-right'  | 'bottom-left'  | 'bottom-center'  | 'bottom-right'const icons = {  success: CircleCheckIcon,  info: InfoIcon,  warning: TriangleAlertIcon,  error: OctagonXIcon,  loading: Loader2Icon,} as constinterface ToastProviderProps extends ToastPrimitive.Provider.Props {  position?: ToastPosition}function StackedToastProvider({  position = 'bottom-right',  children,  ...props}: ToastProviderProps) {  return (    <ToastPrimitive.Provider toastManager={toast} {...props}>      {children}      <Toaster position={position} />    </ToastPrimitive.Provider>  )}function Toaster({  position = 'bottom-right',}: Pick<ToastProviderProps, 'position'>) {  const { toasts } = ToastPrimitive.useToastManager()  let swipeDirection: ToastPrimitive.Root.Props['swipeDirection'] = []  if (position.includes('top')) swipeDirection.push('up')  if (position.includes('bottom')) swipeDirection.push('down')  if (position.includes('left')) swipeDirection.push('left')  if (position.includes('right')) swipeDirection.push('right')  if (position.includes('center')) swipeDirection.push('right', 'left')  return (    <ToastPrimitive.Portal data-slot='toaster-portal'>      <ToastPrimitive.Viewport        data-slot='toaster-viewport'        data-position={position}        className={cn(          'fixed z-50 mx-auto flex w-[calc(100%-var(--toast-inset)*2)] max-w-90 [--toast-inset:--spacing(4)] sm:[--toast-inset:--spacing(8)]',          // Vertical positions          'data-[position*=top]:top-(--toast-inset)',          'data-[position*=bottom]:bottom-(--toast-inset)',          // Horizontal positions          'data-[position*=left]:left-(--toast-inset)',          'data-[position*=center]:left-1/2 data-[position*=center]:-translate-x-1/2',          'data-[position*=right]:right-(--toast-inset)',        )}      >        {toasts.map((toast) => {          const Icon = toast.type            ? icons[toast.type as keyof typeof icons]            : null          return (            <ToastPrimitive.Root              key={toast.id}              data-slot='toast-root'              data-position={position}              data-type={toast.type}              toast={toast}              className={cn(                'group/toast absolute z-[calc(9999-var(--toast-index))] h-(--toast-calc-height) w-full rounded-lg border bg-popover text-popover-foreground shadow-lg/5 select-none',                'data-[type=success]:border-green-600 data-[type=success]:bg-green-100 data-[type=success]:text-green-600 dark:data-[type=success]:border-green-400 dark:data-[type=success]:bg-green-950 dark:data-[type=success]:text-green-400',                'data-[type=error]:border-red-600 data-[type=error]:bg-red-100 data-[type=error]:text-red-600 dark:data-[type=error]:border-red-400 dark:data-[type=error]:bg-red-950 dark:data-[type=error]:text-red-400',                'data-[type=info]:border-blue-600 data-[type=info]:bg-blue-100 data-[type=info]:text-blue-600 dark:data-[type=info]:border-blue-400 dark:data-[type=info]:bg-blue-950 dark:data-[type=info]:text-blue-400',                'data-[type=warning]:border-yellow-600 data-[type=warning]:bg-yellow-100 data-[type=warning]:text-yellow-600 dark:data-[type=warning]:border-yellow-400 dark:data-[type=warning]:bg-yellow-950 dark:data-[type=warning]:text-yellow-400',                '[transition:transform_.5s_cubic-bezier(.22,1,.36,1),opacity_.5s,height_.15s]',                'before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]',                // Base positioning using data-position                'data-[position*=right]:right-0 data-[position*=right]:left-auto',                'data-[position*=left]:right-auto data-[position*=left]:left-0',                'data-[position*=center]:right-0 data-[position*=center]:left-0',                'data-[position*=top]:top-0 data-[position*=top]:bottom-auto data-[position*=top]:origin-top',                'data-[position*=bottom]:top-auto data-[position*=bottom]:bottom-0 data-[position*=bottom]:origin-bottom',                // Gap fill for hover                'after:absolute after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full',                'data-[position*=top]:after:top-full',                'data-[position*=bottom]:after:bottom-full',                // Define some variables                '[--toast-calc-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:--spacing(3)] [--toast-peek:--spacing(3)] [--toast-scale:calc(max(0,1-(var(--toast-index)*.1)))] [--toast-shrink:calc(1-var(--toast-scale))]',                // Define offset-y variable                'data-[position*=top]:[--toast-calc-offset-y:calc(var(--toast-offset-y)+var(--toast-index)*var(--toast-gap)+var(--toast-swipe-movement-y))]',                'data-[position*=bottom]:[--toast-calc-offset-y:calc(var(--toast-offset-y)*-1+var(--toast-index)*var(--toast-gap)*-1+var(--toast-swipe-movement-y))]',                // Default state transform                'data-[position*=top]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]',                'data-[position*=bottom]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]',                // Limited state                'data-limited:opacity-0',                // Expanded state                'data-expanded:h-(--toast-height)',                'data-position:data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(var(--toast-calc-offset-y))]',                // Starting and ending animations                'data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]',                'data-[position*=bottom]:data-starting-style:transform-[translateY(calc(100%+var(--toast-inset)))]',                'data-ending-style:opacity-0',                // Ending animations (direction-aware)                'data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateY(calc(100%+var(--toast-inset)))]',                'data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]',                'data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]',                'data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]',                'data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]',                // Ending animations (expanded)                'data-expanded:data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]',                'data-expanded:data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]',                'data-expanded:data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]',                'data-expanded:data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]',              )}              swipeDirection={swipeDirection}            >              <ToastPrimitive.Content                data-slot='toast-content'                className='flex items-center justify-between gap-0.5 px-4 py-3 transition-opacity data-behind:pointer-events-none data-behind:opacity-0 data-expanded:opacity-100'              >                <div                  data-slot='toast-message'                  className={cn('flex flex-1', {                    'flex-col gap-1': toast.title,                    'flex-row gap-2': !toast.title,                  })}                >                  <div                    data-slot='toast-header'                    className='flex items-center gap-2'                  >                    {Icon && (                      <Icon                        data-slot='toast-icon'                        className='size-4 shrink-0 in-data-[type=loading]:animate-spin in-data-[type=loading]:text-muted-foreground'                      />                    )}                    <ToastPrimitive.Title                      data-slot='toast-title'                      className='text-sm font-medium'                    />                  </div>                  <ToastPrimitive.Description                    data-slot='toast-description'                    className='text-sm not-in-data-type:text-muted-foreground in-data-[type=loading]:text-muted-foreground'                  />                </div>                {toast.actionProps && (                  <ToastPrimitive.Action                    data-slot='toast-action'                    className={cn(                      buttonVariants({ variant: 'ghost', size: 'xs' }),                      'hover:bg-current/20 hover:text-current dark:hover:bg-current/20',                    )}                    {...toast.actionProps}                  />                )}              </ToastPrimitive.Content>            </ToastPrimitive.Root>          )        })}      </ToastPrimitive.Viewport>    </ToastPrimitive.Portal>  )}function AnchoredToastProvider({ children, ...props }: ToastProviderProps) {  return (    <ToastPrimitive.Provider toastManager={anchoredToast} {...props}>      {children}      <AnchoredToaster />    </ToastPrimitive.Provider>  )}function AnchoredToaster() {  const { toasts } = ToastPrimitive.useToastManager()  return (    <ToastPrimitive.Portal data-slot='anchored-toaster-portal'>      <ToastPrimitive.Viewport        className='outline-none'        data-slot='anchored-toast-viewport'      >        {toasts.map((toast) => {          const positionerProps = toast.positionerProps          if (!positionerProps?.anchor) return null          const Icon = toast.type            ? icons[toast.type as keyof typeof icons]            : null          const tooltipStyle =            (toast.data as { tooltipStyle?: boolean })?.tooltipStyle ?? false          return (            <ToastPrimitive.Positioner              key={toast.id}              data-slot='anchored-toast-positioner'              sideOffset={positionerProps.sideOffset ?? 4}              toast={toast}              className='z-50 max-w-[min(--spacing(64),var(--available-width))]'            >              <ToastPrimitive.Root                data-slot='anchored-toast-root'                className={cn(                  'border bg-popover text-popover-foreground shadow-lg/5',                  'data-[type=success]:border-green-600 data-[type=success]:bg-green-100 data-[type=success]:text-green-600 dark:data-[type=success]:border-green-400 dark:data-[type=success]:bg-green-950 dark:data-[type=success]:text-green-400',                  'data-[type=error]:border-red-600 data-[type=error]:bg-red-100 data-[type=error]:text-red-600 dark:data-[type=error]:border-red-400 dark:data-[type=error]:bg-red-950 dark:data-[type=error]:text-red-400',                  'data-[type=info]:border-blue-600 data-[type=info]:bg-blue-100 data-[type=info]:text-blue-600 dark:data-[type=info]:border-blue-400 dark:data-[type=info]:bg-blue-950 dark:data-[type=info]:text-blue-400',                  'data-[type=warning]:border-yellow-600 data-[type=warning]:bg-yellow-100 data-[type=warning]:text-yellow-600 dark:data-[type=warning]:border-yellow-400 dark:data-[type=warning]:bg-yellow-950 dark:data-[type=warning]:text-yellow-400',                  'relative transition-[scale,opacity] not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/6%)] data-ending-style:scale-98 data-ending-style:opacity-0 data-starting-style:scale-98 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]',                  tooltipStyle                    ? 'rounded-md before:rounded-[calc(var(--radius-md)-1px)]'                    : 'rounded-lg before:rounded-[calc(var(--radius-lg)-1px)]',                )}                toast={toast}              >                {tooltipStyle ? (                  <ToastPrimitive.Content                    data-slot='anchored-toast-tooltip-content'                    className='pointer-events-auto px-2 py-1'                  >                    <ToastPrimitive.Title data-slot='anchored-toast-title' />                  </ToastPrimitive.Content>                ) : (                  <ToastPrimitive.Content                    data-slot='anchored-toast-content'                    className='flex items-center justify-between gap-0.5 px-2 py-3 transition-opacity data-behind:pointer-events-none data-behind:opacity-0 data-expanded:opacity-100'                  >                    <div                      data-slot='anchored-toast-message'                      className={cn('flex flex-1', {                        'flex-col gap-1': toast.title,                        'flex-row gap-2': !toast.title,                      })}                    >                      <div                        data-slot='anchored-toast-header'                        className='flex items-center gap-2'                      >                        {Icon && (                          <Icon                            data-slot='anchored-toast-icon'                            className='size-4 shrink-0 in-data-[type=loading]:animate-spin in-data-[type=loading]:text-muted-foreground'                          />                        )}                        <ToastPrimitive.Title                          data-slot='anchored-toast-title'                          className='text-xs font-medium'                        />                      </div>                      <ToastPrimitive.Description                        data-slot='anchored-toast-description'                        className='text-xs not-in-data-type:text-muted-foreground in-data-[type=loading]:text-muted-foreground'                      />                    </div>                    {toast.actionProps && (                      <ToastPrimitive.Action                        data-slot='anchored-toast-action'                        className={cn(                          buttonVariants({ variant: 'ghost', size: 'xs' }),                          'hover:bg-current/20 hover:text-current dark:hover:bg-current/20',                        )}                        {...toast.actionProps}                      />                    )}                  </ToastPrimitive.Content>                )}              </ToastPrimitive.Root>            </ToastPrimitive.Positioner>          )        })}      </ToastPrimitive.Viewport>    </ToastPrimitive.Portal>  )}export { toast, anchoredToast, StackedToastProvider, AnchoredToastProvider }

On this page