Basic
Wrap the tree in a Toast.Provider, render a Toast.Viewport that maps useToast().toasts into Toast.Roots, and raise a toast from anywhere under the provider with the manager. Toast.Content stacks the Title and Description; Toast.Close dismisses it. The provider runs the auto-dismiss timer (5 seconds by default) and announces neutral/info/success/loading toasts politely, while warning/danger toasts use an assertive live region.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { X } from "lucide-react";
function Trigger() {
const toast = useToast();
return (
<Button
variant="outline"
onClick={() => toast.show({ title: "Link copied", description: "The page URL is on your clipboard." })}
>
Show toast
</Button>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
))}
</Toast.Viewport>
);
}
export default function ToastBasic() {
return (
<Toast.Provider>
<Trigger />
<Toasts />
</Toast.Provider>
);
}
Status accents
The typed helpers — info, success, warning, and danger — set the toast's type, which paints any Toast.Icon in the matching status role. Colour is reserved for outcomes, so a plain show toast and an info toast both carry a neutral icon: "copied to clipboard" is an acknowledgement, and painting it would make blue the resting state with nothing left for success, warning, and danger to stand out against. A leading icon is the canonical, scannable status cue; map each type to its glyph in your renderer.
"use client";
import { Icon } from "@stridge/noctis";
import type { IconGlyph } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { CircleAlert, CircleCheck, Info, TriangleAlert, X } from "lucide-react";
// The canonical, scannable cue: a per-type leading glyph that inherits the toast's status colour.
const STATUS_ICONS: Record<string, IconGlyph> = {
info: Info,
success: CircleCheck,
warning: TriangleAlert,
danger: CircleAlert,
};
function Triggers() {
const toast = useToast();
return (
<div className="flex flex-wrap gap-2">
<Button variant="outline" onClick={() => toast.info("Sync started", { description: "Pulling the latest changes." })}>
Info
</Button>
<Button variant="outline" onClick={() => toast.success("Saved", { description: "Your changes are live." })}>
Success
</Button>
<Button variant="outline" onClick={() => toast.warning("Storage almost full", { description: "92% used." })}>
Warning
</Button>
<Button variant="outline" onClick={() => toast.danger("Upload failed", { description: "The connection dropped." })}>
Danger
</Button>
</div>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => {
const glyph = entry.type ? STATUS_ICONS[entry.type] : undefined;
return (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
{glyph ? (
<Toast.Icon>
<Icon icon={glyph} />
</Toast.Icon>
) : null}
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
);
})}
</Toast.Viewport>
);
}
export default function ToastStatuses() {
return (
<Toast.Provider>
<Triggers />
<Toasts />
</Toast.Provider>
);
}
Leading icon
Toast.Icon is the optional leading slot — the toast's single chromatic signal. Compose the glyph as its child and it inherits the toast's status colour centrally, so the icon is the one accent, never two. It is decorative (aria-hidden); the title carries the accessible message.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { CircleCheck, X } from "lucide-react";
function Trigger() {
const toast = useToast();
return (
<Button variant="outline" onClick={() => toast.success("Deployment live", { description: "v2.4.0 is serving traffic." })}>
Deploy
</Button>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
{/* `Toast.Icon` inherits the toast's status colour, so the glyph reads as the signal. */}
<Toast.Icon>
<Icon icon={CircleCheck} />
</Toast.Icon>
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
))}
</Toast.Viewport>
);
}
export default function ToastWithIcon() {
return (
<Toast.Provider>
<Trigger />
<Toasts />
</Toast.Provider>
);
}
Loading & promise
useToast().promise(p, { loading, success, error }) drives one toast through an async flow: it shows the loading message with Toast.Spinner, then swaps that same toast to the success or error result as the promise settles — never stacking a single flow. The success/error mappers receive the resolved value or the error.
"use client";
import { Icon } from "@stridge/noctis";
import type { IconGlyph } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { CircleAlert, CircleCheck, X } from "lucide-react";
// `promise()` stamps `loading`, then `success` or `error` on the same toast as it settles.
const RESULT_ICONS: Record<string, IconGlyph> = {
success: CircleCheck,
error: CircleAlert,
};
function Trigger() {
const toast = useToast();
function run() {
const work = new Promise<string>((resolve, reject) => {
const ok = Math.random() > 0.3;
setTimeout(() => (ok ? resolve("3 files") : reject(new Error("the connection dropped"))), 1800);
});
toast.promise(work, {
loading: "Uploading…",
success: (count) => `Uploaded ${count}`,
error: (err: Error) => `Upload failed: ${err.message}`,
});
}
return (
<Button variant="outline" onClick={run}>
Upload
</Button>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => {
const glyph = entry.type ? RESULT_ICONS[entry.type] : undefined;
return (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
{entry.type === "loading" ? (
<Toast.Spinner />
) : glyph ? (
<Toast.Icon>
<Icon icon={glyph} />
</Toast.Icon>
) : null}
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
);
})}
</Toast.Viewport>
);
}
export default function ToastPromise() {
return (
<Toast.Provider>
<Trigger />
<Toasts />
</Toast.Provider>
);
}
For a flow you settle yourself, loading(title) raises a persistent loading toast (no auto-dismiss); resolve it in place with update(id, …) or close(id). The spinner is neutral and goes static under reduced motion.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { CircleCheck, X } from "lucide-react";
function Trigger() {
const toast = useToast();
function save() {
// A `loading` toast is persistent (no auto-dismiss) — settle it in place with `update`.
const id = toast.loading("Saving draft…");
setTimeout(() => {
toast.update(id, { type: "success", title: "Draft saved", timeout: 5000 });
}, 1600);
}
return (
<Button variant="outline" onClick={save}>
Save draft
</Button>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
{entry.type === "loading" ? (
<Toast.Spinner />
) : entry.type === "success" ? (
<Toast.Icon>
<Icon icon={CircleCheck} />
</Toast.Icon>
) : null}
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
))}
</Toast.Viewport>
);
}
export default function ToastLoading() {
return (
<Toast.Provider>
<Trigger />
<Toasts />
</Toast.Provider>
);
}
Actions & persistence
A toast has no action part of its own — actions are plain Buttons composed into the text column, so the whole button surface (variants, sizes, ButtonGroup) is available without a second, weaker affordance to learn. Anything you put under the copy lands in that column. A toast with an action must stay reachable, so pass timeout: 0 to keep it up until acted on (an auto-dismissing toast can vanish before a slow reader reacts). Any caller timeout below the 5-second floor is raised to it.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { X } from "lucide-react";
function Trigger() {
const toast = useToast();
return (
<Button
variant="outline"
onClick={() =>
toast.show({
title: "Message archived",
description: "It moved to the archive.",
// A persistent toast — `timeout: 0` disables the auto-dismiss so the action stays reachable.
timeout: 0,
})
}
>
Archive
</Button>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
{/* Actions are plain `Button`s composed into the text column — the card has no action part
of its own, so the full button surface (variants, sizes, groups) is available here. */}
<div className="mt-1 flex gap-2">
<Button variant="secondary" size="xs" onClick={() => toast.dismiss(entry.id)}>
Undo
</Button>
</div>
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
))}
</Toast.Viewport>
);
}
export default function ToastWithAction() {
return (
<Toast.Provider>
<Trigger />
<Toasts />
</Toast.Provider>
);
}
The canonical destructive pattern pairs the action with Undo: delete optimistically, raise a persistent toast (timeout: 0) whose action restores the item. Mark those toasts through data so the viewport knows which ones carry an Undo.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { Trash2, X } from "lucide-react";
/** Marks the toasts this flow raises, so the viewport knows which ones carry an Undo. */
interface DeletionData {
undoable: true;
}
function Trigger() {
const toast = useToast();
function remove() {
// A destructive action pairs with a persistent Undo: `timeout: 0` keeps the toast up until acted on.
toast.show({
title: "Issue deleted",
description: "“Fix the login redirect” was removed.",
timeout: 0,
data: { undoable: true } satisfies DeletionData,
});
}
return (
<Button variant="outline" onClick={remove}>
<Icon icon={Trash2} size="sm" />
Delete issue
</Button>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => {
const data = entry.data as DeletionData | undefined;
function undo() {
toast.dismiss(entry.id);
toast.success("Restored", { description: "The issue is back." });
}
return (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
{data?.undoable ? (
<div className="mt-1 flex gap-2">
<Button variant="secondary" size="xs" onClick={undo}>
Undo
</Button>
</div>
) : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
);
})}
</Toast.Viewport>
);
}
export default function ToastUndo() {
return (
<Toast.Provider>
<Trigger />
<Toasts />
</Toast.Provider>
);
}
Placement
Toast.Viewport docks via placement (default bottom-end) — a logical corner, so *-start/*-end flip under RTL. Top placements enter from above and stack downward; bottom placements lift off the docked edge. The insets are tokenised per axis through --noctis-toast-viewport-padding-block and --noctis-toast-viewport-padding-inline, so an app can lift the stack clear of bottom chrome — a footer, a docked assistant — without also pulling it in from the side.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import type { ToastPlacement } from "@stridge/noctis/toast";
import { X } from "lucide-react";
import { useState } from "react";
const PLACEMENTS: ToastPlacement[] = ["top-start", "top-center", "top-end", "bottom-start", "bottom-center", "bottom-end"];
function Triggers({ onPick }: { onPick: (placement: ToastPlacement) => void }) {
const toast = useToast();
return (
<div className="grid w-full max-w-sm grid-cols-3 gap-2">
{PLACEMENTS.map((placement) => (
<Button
key={placement}
variant="outline"
size="sm"
onClick={() => {
onPick(placement);
toast.info(placement, { description: "Docked here." });
}}
>
{placement}
</Button>
))}
</div>
);
}
function Toasts({ placement }: { placement: ToastPlacement }) {
const toast = useToast();
return (
<Toast.Viewport placement={placement}>
{toast.toasts.map((entry) => (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
))}
</Toast.Viewport>
);
}
export default function ToastPositions() {
const [placement, setPlacement] = useState<ToastPlacement>("bottom-end");
return (
<Toast.Provider>
<Triggers onPick={setPlacement} />
<Toasts placement={placement} />
</Toast.Provider>
);
}
Anchored toasts
For contextual feedback pinned to a trigger (a transient "Copied" bubble), wrap each toast in Toast.Positioner and pass positionerProps: { anchor } when raising it. The positioner takes over placement (mirroring popup positioning), so the toast leaves the stacked corner and sizes to its content. Keep anchored toasts in their own viewport, separate from the stacked queue.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { Copy } from "lucide-react";
import { useRef } from "react";
function CopyButton() {
const toast = useToast();
const anchorRef = useRef<HTMLSpanElement>(null);
return (
<span ref={anchorRef} className="inline-flex">
<Button
variant="outline"
onClick={() =>
// `positionerProps.anchor` pins this toast to the trigger instead of the stacked corner.
toast.show({
description: "Copied to clipboard",
timeout: 1500,
positionerProps: { anchor: anchorRef.current, side: "top", sideOffset: 8 },
})
}
>
<Icon icon={Copy} size="sm" />
Copy link
</Button>
</span>
);
}
function AnchoredToasts() {
const toast = useToast();
return (
<Toast.Portal>
<Toast.Viewport>
{toast.toasts.map((entry) => (
<Toast.Positioner key={entry.id} toast={entry}>
<Toast.Root toast={entry}>
<Toast.Content>
<Toast.Description>{entry.description}</Toast.Description>
</Toast.Content>
</Toast.Root>
</Toast.Positioner>
))}
</Toast.Viewport>
</Toast.Portal>
);
}
export default function ToastAnchored() {
return (
<Toast.Provider>
<CopyButton />
<AnchoredToasts />
</Toast.Provider>
);
}
Custom data
Pass any typed object as data when raising a toast and read it back in the renderer to drive custom content — a mention, a thumbnail, a progress payload. The data rides the toast object Base UI tracks.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { X } from "lucide-react";
interface MentionData {
user: string;
project: string;
}
function Trigger() {
const toast = useToast();
return (
<Button
variant="outline"
onClick={() =>
toast.show({
title: "New mention",
data: { user: "Ada Lovelace", project: "Q3 planning" } satisfies MentionData,
})
}
>
Mention me
</Button>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => {
// Any typed object passed as `data` is available here to drive custom rendering.
const data = entry.data as MentionData | undefined;
return (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
<Toast.Title>{entry.title}</Toast.Title>
{data ? (
<Toast.Description>
{data.user} mentioned you in “{data.project}”.
</Toast.Description>
) : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
);
})}
</Toast.Viewport>
);
}
export default function ToastCustomData() {
return (
<Toast.Provider>
<Trigger />
<Toasts />
</Toast.Provider>
);
}
Varying heights
Toasts of different heights stack cleanly: Toast.Content clips overflow so the collapsed peek clamps every toast to the frontmost one's height, then each expands to its natural height as the stack fans out on hover or focus.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { X } from "lucide-react";
import { useState } from "react";
const MESSAGES = [
"Short message.",
"A slightly longer message that wraps onto a second line in the toast.",
"A much longer description that intentionally takes several lines, so you can see the collapsed stack clamp every toast to the front one's height and expand smoothly on hover or focus.",
];
function Trigger() {
const toast = useToast();
const [n, setN] = useState(0);
return (
<Button
variant="outline"
onClick={() => {
toast.show({ title: `Toast ${n + 1}`, description: MESSAGES[n % MESSAGES.length] });
setN((prev) => prev + 1);
}}
>
Add toast
</Button>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
))}
</Toast.Viewport>
);
}
export default function ToastVaryingHeights() {
return (
<Toast.Provider>
<Trigger />
<Toasts />
</Toast.Provider>
);
}
Deduplicating
Raising a toast with an id that already exists updates it in place and refreshes its timer instead of stacking a duplicate — ideal for a recurring status like “Draft saved”. The toast's updateKey increments on each upsert, so a renderer can replay an attention cue.
"use client";
import { Icon } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Toast, useToast } from "@stridge/noctis/toast";
import { X } from "lucide-react";
function Trigger() {
const toast = useToast();
return (
<Button
variant="outline"
onClick={() =>
// Re-adding with the same `id` updates the live toast in place (and refreshes its timer)
// instead of stacking a duplicate. `entry.updateKey` increments on each upsert.
toast.show({
id: "save-status",
title: "Draft saved",
description: "Click again while it is visible — it updates in place, never stacks.",
})
}
>
Save draft
</Button>
);
}
function Toasts() {
const toast = useToast();
return (
<Toast.Viewport>
{toast.toasts.map((entry) => (
<Toast.Root key={entry.id} toast={entry}>
<Toast.Content>
<Toast.Title>{entry.title}</Toast.Title>
{entry.description ? <Toast.Description>{entry.description}</Toast.Description> : null}
<Toast.Close aria-label="Dismiss">
<Icon icon={X} />
</Toast.Close>
</Toast.Content>
</Toast.Root>
))}
</Toast.Viewport>
);
}
export default function ToastDeduplicated() {
return (
<Toast.Provider>
<Trigger />
<Toasts />
</Toast.Provider>
);
}
The manager
useToast() returns a typed façade over the live queue, callable from any component under Toast.Provider. Like Sheet's useSheetStack, the state is owned upstream (here, Base UI's provider) and reached through context.
toasts— the mounted toasts, front to back; map them intoToast.Roots inside the viewport.count— the number of mounted toasts (≈ Sheet'sdepth).show(options)— raise a neutral toast with the full option set (title,description,timeout,priority,data, …). A callertimeoutis floored to 5000ms; pass0to keep it up. Returns the toast's id.info/success/warning/danger(title, options?) — raise a status-typed toast; the title is positional.warning/dangerannounce assertively, the rest politely.loading(title, options?)— raise a persistent loading toast (renderToast.Spinnerfor it), then settle it withupdate/close.dismiss(id?)— dismiss a toast by id; omit the id to clear every toast (Base UI closes the whole stack).clear()— clear the whole stack at once (≈ Sheet'sreset()); equivalent todismiss()with no id.update(id, options)— update a live toast in place, refreshing its timer.promise(promise, { loading, success, error })— drive a single toast through a promise's lifecycle.
Accessibility
- Politeness follows status.
dangerandwarningare announced assertively (APGalert— they interrupt);info,success,loading, and neutral toasts are polite (status— they wait for idle). The manager maps this for you; for a customshow, setpriority: 'high'when the message is urgent. - Slow readers come first. Toasts never auto-dismiss faster than 5 seconds, and a toast with an action should set
timeout: 0so it stays reachable. Screen-zoom users can miss a toast entirely — keep critical information off transient surfaces. - Focus. F6 moves focus into the viewport and lands on the front toast with a visible ring; focus restores to the page when the last toast closes. Toasts are never tab-trapped.
- Copy conventions. Write success as
{Noun} {past-participle}(“Domain added”), sentence case, no trailing period; end errors with a recovery step. Keep to one tertiary action.
Keyboard
| Key | Action |
|---|---|
| F6 | Move focus into the toast viewport from anywhere on the page (lands on the front toast). |
| Tab / Shift + Tab | Move focus between a focused toast's action and close buttons. |
| Esc | With the viewport focused, dismiss the focused toast. |
| Enter / Space | Activate the focused action or close button. |
Toasts can also be dismissed by swipe — toward the docked corner (down, or to the end edge, which flips under RTL). Hovering or focusing the stack pauses every auto-dismiss timer and fans the toasts out so each is readable.
Anatomy
Compose a toast from its parts. Toast.Provider owns the queue and the timers (timeout, default 5000ms — 0 disables auto-dismiss; limit, default 3), and Toast.Viewport is the live region that holds the stack.
Toast.Provider— hosts the manager and timers, and resolves the translated viewport label. Renders no element of its own.Toast.Viewport— the fixed live region;placementdocks it (defaultbottom-end,data-placement) anddata-expandedmarks it fanned out.Toast.Root— groups one toast on an elevatedSurface. Pass thetoastobject fromuseToast().toasts; itstypedrives the leading status icon's colour (data-type). Swipe-to-dismiss runs toward the docked corner (RTL-resolved).Toast.Content— the card body: a grid of the leading icon, the text column, and the close button. Composed children land in the text column, so an actionButtonneeds no placement of its own. It holds everything visible, so the collapsed stack can cleanly blank the cards behind the front one (data-behind).Toast.Title+Toast.Description— the accessible name and supporting copy, linked to the root viaaria-labelledbyandaria-describedby.Toast.Icon— an optional leading status glyph that inherits the toast's colour (decorative).Toast.Spinner— the neutral loading spinner for theloadingphase (role="status").Toast.Close— the dismiss button; give it anaria-label. It carries the card's own close styling, so leave it as the part rather than composing aButtoninto it.Toast.Portal/Toast.Positioner/Toast.Arrow— for anchored toasts pinned to a trigger.
Every rendered part carries a data-slot (noctis-toast-viewport, noctis-toast, noctis-toast-content, noctis-toast-title, noctis-toast-description, noctis-toast-icon, noctis-toast-spinner, noctis-toast-close) for host-side styling — the root also carries data-type, the viewport data-placement. Pair it with the Base UI state attributes (data-expanded, data-behind, data-limited, data-swiping, data-swipe-direction, data-starting-style, data-ending-style). The root renders through Surface at elevated elevation, so controls inside re-derive off that base and separate cleanly.
Design tokens
Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. The minted tokens are the public override seam: set one on any ancestor and every toast in that region retunes — e.g. .notifications { --noctis-toast-viewport-gap: 0.5rem; } tightens the gap between fanned-out toasts beneath it. Knobs that aren't minted are reached through the part's data-slot. See Customization for the full override ladder and Tokens for the whole graph.
API reference
Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table; parts that only forward to Base UI's Toast list just the props they pass through. Expand a row for the full type and description.