Toast
A notification component for displaying transient messages with multiple variants.
Docsimport { 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 toastManual
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.