Toast

A notification component for displaying transient messages with multiple variants.

Docs
import { Button, createToaster, Toaster } from "~/components/toast";

const toaster = createToaster({
  placement: "bottom-end",
});

export default function ToastBasicDemo() {
  return (
    <div>
      <Button onClick={() => toaster.create({ title: "Hello", description: "World" })}>
        Show Toast
      </Button>
      <Toaster toaster={toaster} />
    </div>
  );
}

Installation

CLI

Run the following command to add the component to your project:

npx @ark-preset/cli@latest add toast

Manual

Create the recipe file at src/components/recipes/toast.ts:

import { tv, type VariantProps } from "tailwind-variants";

export const toastVariants = tv({
  slots: {
    root: "group pointer-events-auto relative flex w-full min-w-sm max-w-sm items-center justify-between gap-x-4 overflow-hidden rounded-md outline-2 outline-border p-4 shadow-lg bg-background/80 backdrop-blur-3xl [translate:var(--x)_var(--y)] [scale:var(--scale)] [height:var(--height)] [opacity:var(--opacity)] [z-index:var(--z-index)] will-change-[translate,opacity,scale] transition-[translate,scale,opacity,height] [transition-timing-function:cubic-bezier(0.21,1.02,0.73,1)] duration-400 data-[state=closed]:[transition-timing-function:cubic-bezier(0.06,0.71,0.55,1)]",
    title: "text-sm font-semibold",
    description: "text-sm opacity-90",
    closeTrigger:
      "absolute right-1 top-1 p-1 text-foreground/50 transition-opacity group-hover:text-foreground focus:outline-none focus:ring-2 size-5 cursor-pointer grid place-content-center rounded",
    actionTrigger:
      "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-xs font-medium transition duration-300 motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0.5 active:duration-150 group-hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring",
  },
  variants: {
    variant: {
      default: { root: "outline-border" },
      loading: { root: "outline-border" },
      error: {
        root: "outline-red-500 text-red-950 dark:outline-red-900 dark:text-red-50",
      },
      success: {
        root: "outline-green-500 text-green-950 dark:outline-green-900 dark:text-green-50",
      },
      info: { root: "outline-border" },
      warning: {
        root: "outline-yellow-500 text-yellow-950 dark:outline-yellow-900 dark:text-yellow-50",
      },
    },
  },
  defaultVariants: { variant: "default" },
});

export type ToastVariants = VariantProps<typeof toastVariants>;

Create the component directory and files.

src/components/toast/toast.base.tsx:

import { Toast as ArkToast } from "@ark-ui/solid/toast";
import { splitProps, type Component } from "solid-js";
import { type ToastVariants, toastVariants } from "../recipes/toast";

const styles = toastVariants();

const Root: Component<ArkToast.RootProps & ToastVariants> = (props) => {
  const [local, others] = splitProps(props, ["class", "variant"]);
  return (
    <ArkToast.Root
      class={styles.root({ class: local.class, variant: local.variant })}
      {...others}
    />
  );
};

const Title: Component<ArkToast.TitleProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkToast.Title class={styles.title({ class: local.class })} {...others} />;
};

const Description: Component<ArkToast.DescriptionProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkToast.Description class={styles.description({ class: local.class })} {...others} />;
};

const CloseTrigger: Component<ArkToast.CloseTriggerProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkToast.CloseTrigger class={styles.closeTrigger({ class: local.class })} {...others} />;
};

const ActionTrigger: Component<ArkToast.ActionTriggerProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return (
    <ArkToast.ActionTrigger class={styles.actionTrigger({ class: local.class })} {...others} />
  );
};

export const Toast = { Root, Title, Description, CloseTrigger, ActionTrigger };

src/components/toast/index.tsx:

import {
  Toaster as ArkToaster,
  createToaster,
  type CreateToasterReturn,
} from "@ark-ui/solid/toast";
import { splitProps, type Component } from "solid-js";
import { Toast as ToastBase } from "./toast.base";
import { type ToastVariants } from "../recipes/toast";
import { Portal } from "solid-js/web";

type ToasterProps = {
  toaster: CreateToasterReturn;
  class?: string;
};

const Toaster: Component<ToasterProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return (
    <Portal>
      <ArkToaster class={local.class} {...others}>
        {(toast) => (
          <ToastBase.Root variant={(toast().type as ToastVariants["variant"]) || "default"}>
            <div class="grid gap-1">
              {toast().title && <ToastBase.Title>{toast().title}</ToastBase.Title>}
              {toast().description && (
                <ToastBase.Description>{toast().description}</ToastBase.Description>
              )}
            </div>
            <ToastBase.CloseTrigger>✕</ToastBase.CloseTrigger>
            {toast().action && (
              <ToastBase.ActionTrigger>{toast().action?.label}</ToastBase.ActionTrigger>
            )}
          </ToastBase.Root>
        )}
      </ArkToaster>
    </Portal>
  );
};

export { Toaster, createToaster, ToastBase };

export { toastVariants, type ToastVariants } from "../recipes/toast";

Note: Make sure your project has the Tailwind CSS theme variables set up (--background, --foreground, --ring, --border, etc.) or override the utility classes to match your design system.

Usage

Basic Usage

import { Button, createToaster, Toaster } from "~/components/toast";

const toaster = createToaster({
  placement: "bottom-end",
});

export default function ToastBasicDemo() {
  return (
    <div>
      <Button onClick={() => toaster.create({ title: "Hello", description: "World" })}>
        Show Toast
      </Button>
      <Toaster toaster={toaster} />
    </div>
  );
}

With Action

Add an action button to the toast using the action property.

import { Button, createToaster, Toaster } from "~/components/toast";

const toaster = createToaster({
  placement: "top-end",
});

export default function ToastActionDemo() {
  return (
    <div>
      <Button
        onClick={() =>
          toaster.create({
            title: "File deleted",
            description: "Your file has been removed.",
            action: { label: "Undo", onClick: () => console.log("undo") },
          })
        }
      >
        Delete File
      </Button>
      <Toaster toaster={toaster} />
    </div>
  );
}

Variants

Use the type property to change the visual style.

import { Button, createToaster, Toaster } from "~/components/toast";

const toaster = createToaster({
  placement: "bottom",
});

export default function ToastVariantsDemo() {
  return (
    <div class="flex flex-wrap gap-2">
      <Button
        onClick={() =>
          toaster.create({ title: "Default toast", description: "This is a default message" })
        }
      >
        Default
      </Button>
      <Button
        variant="secondary"
        onClick={() =>
          toaster.create({
            title: "Info toast",
            description: "This is an info message",
            type: "info",
          })
        }
      >
        Info
      </Button>
      <Button
        variant="secondary"
        onClick={() =>
          toaster.create({
            title: "Success toast",
            description: "Operation completed!",
            type: "success",
          })
        }
      >
        Success
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toaster.create({
            title: "Warning toast",
            description: "Please check this",
            type: "warning",
          })
        }
      >
        Warning
      </Button>
      <Button
        variant="destructive"
        onClick={() =>
          toaster.create({
            title: "Error toast",
            description: "Something went wrong",
            type: "error",
          })
        }
      >
        Error
      </Button>
      <Toaster toaster={toaster} />
    </div>
  );
}

Advanced Usage

When you need more control, import raw primitive parts directly from the base file:

import { Toast } from "~/components/toast/toast.base";

Or import ToastBase (the raw parts namespace) from the composite entry point:

import { ToastBase } from "~/components/toast";

API Reference

See the Ark UI Toast documentation.