Toast
Overview
The Toast is a transient, floating feedback element used for notifications, status updates, and confirmations that appear above the interface and dismiss themselves. The useToastRecipe() composable creates a fully configured recipe with variant, size, and orientation options for the body, while the semantic color accent lands on the two parts that carry it — the leading icon and the progress bar.
The Toast recipe descends from the Callout recipe and shares its color, size, and orientation vocabulary — the same nine colors, three sizes, and two orientations. It differs in three ways. A toast floats: an elevation shadow lifts it off the page and it sizes to its content, where a callout is an inline, full-width surface. It trims the variant axis to the three filled styles — solid, soft, subtle — dropping callout's outline, since a transparent surface floating over arbitrary page content would be illegible. And, following Nuxt UI, the toast body stays neutral for every color: the color axis tints only the icon and progress bar, not the surface, so a success toast is a clean white card with a green icon and green countdown — not a green card.
The Toast recipe integrates directly with the default design tokens preset and generates type-safe utility classes at build time with zero runtime CSS.
Why use the Toast recipe?
The Toast recipe helps you:
- Ship faster with sensible defaults: Get 9 accent colors, 3 visual styles, 3 sizes, and 2 orientations out of the box with a single composable call.
- Keep the surface calm: The body stays neutral for every color, so stacked toasts read as one coherent set; only the icon and countdown carry the semantic accent.
- Read as a floating surface: A soft, diffuse
@box-shadow.lgelevation and well-rounded@border-radius.lgcorners lift the toast off the page so it reads as a transient notification, not an inline notice. - Customize without forking: Override base styles, default variants, or filter out options you don't need — all through the options API.
- Stay type-safe: Full TypeScript support means your editor catches invalid color, variant, size, or orientation values at compile time.
- Integrate with your tokens: Every value references the design tokens preset, so theme changes propagate automatically.
Usage
Register the recipe
Add the Toast recipe to a local Styleframe instance. The global styleframe.config.ts provides design tokens and utilities, while the component-level file registers the recipe itself:
import { styleframe } from 'virtual:styleframe';
import {
useToastContentRecipe,
useToastDescriptionRecipe,
useToastDismissRecipe,
useToastIconRecipe,
useToastProgressRecipe,
useToastRecipe,
useToastTitleRecipe,
} from '@styleframe/theme';
const s = styleframe();
const toast = useToastRecipe(s);
const toastIcon = useToastIconRecipe(s);
const toastContent = useToastContentRecipe(s);
const toastTitle = useToastTitleRecipe(s);
const toastDescription = useToastDescriptionRecipe(s);
const toastProgress = useToastProgressRecipe(s);
const toastDismiss = useToastDismissRecipe(s);
export default s;
Build the component
Import the toast runtime function from the virtual module and pass variant props to compute class names:
import { toast, toastContent, toastDescription, toastDismiss, toastIcon, toastTitle, type ToastProps, type ToastIconProps } from "virtual:styleframe";
// `color` is not a body prop — it lives on the icon/progress recipes — so it is
// declared here from `ToastIconProps`, not inherited from `ToastProps`.
interface ToastComponentProps extends ToastProps {
color?: ToastIconProps["color"];
title?: string;
description?: string;
icon?: React.ReactNode;
dismissible?: boolean;
onDismiss?: () => void;
children?: React.ReactNode;
}
export function Toast({
color = "neutral",
variant = "solid",
size = "md",
orientation = "horizontal",
title,
description,
icon,
dismissible = false,
onDismiss,
children,
}: ToastComponentProps) {
// color is scoped to the icon and progress bar; the body keys off variant only
const classes = toast({ variant, size, orientation });
return (
<div className={classes} role="status">
{icon && <span className={toastIcon({ color })} aria-hidden="true">{icon}</span>}
<div className={toastContent()}>
{title && <strong className={toastTitle({ size })}>{title}</strong>}
{description && <p className={toastDescription({ size })}>{description}</p>}
{children}
</div>
{dismissible && (
<button type="button" className={toastDismiss()} onClick={onDismiss} aria-label="Dismiss">
×
</button>
)}
</div>
);
}
See it in action
Colors
The Toast recipe offers 9 accent colors: the 6 semantic colors (primary, secondary, success, info, warning, error) plus 3 neutral-spectrum colors (light, dark, neutral). Following Nuxt UI, the accent is scoped to the icon and the progress bar — the toast body stays neutral for every color. So a success toast is a clean neutral card with a green icon and a green countdown, never a green surface. Pass the color to the toast component; it routes to toastIcon({ color }) and toastProgress({ color }) under the hood. Because both parts paint with currentColor, an SVG or icon-font glyph picks up the accent automatically.
The neutral body itself is set by the variant axis (see Variants), and adapts to the color scheme: light in light mode, dark in dark mode.
Color Reference
The token below is the icon/progress accent each color carries (the body stays neutral regardless):
| Color | Accent token | Use Case |
|---|---|---|
primary | @color.primary | Primary brand notifications, key highlights |
secondary | @color.secondary | Secondary information, supporting messages |
success | @color.success | Positive feedback, completions, confirmations |
info | @color.info | Informational messages, tips, notices |
warning | @color.warning | Caution messages, pending states |
error | @color.error | Error messages, failed actions, critical alerts |
light | @color.text-inverted (adaptive) | Minimal-weight accent; matches the family's outline light foreground |
dark | @color.text (adaptive) | High-contrast neutral accent |
neutral | @color.text (adaptive) | Default. Accent matches the body's neutral foreground |
Variants
Three visual style variants control the toast's neutral surface — the treatment is the same for every color, since the accent lives on the icon and progress bar, not the body. Unlike the callout it descends from, a toast has no outline variant — a floating surface with a transparent background would be illegible over arbitrary page content, so every toast variant is a filled neutral card.
Solid
An opaque neutral card — white in light mode, near-black in dark mode — with a hairline border. The highest-contrast surface, matching Nuxt UI's default toast; ideal for critical notifications that demand immediate attention. This is the default variant for the Toast recipe.
Soft
A faint neutral fill with no border. A gentle, low-elevation style that works well for informational messages and tips.
Subtle
A faint neutral fill with a hairline border. Combines the softness of the soft variant with a defining border for a little more structure.
Sizes
Three size variants from sm to lg control the font size, padding, and gap of the toast.
Size Reference
| Size | Title | Description | Padding (V / H) | Gap |
|---|---|---|---|---|
sm | @font-size.sm | @font-size.xs | @0.5 / @0.75 | @0.5 |
md | @font-size.md | @font-size.sm | @1 / @1.25 | @1 |
lg | @font-size.lg | @font-size.md | @1.25 / @1.5 | @1.25 |
The title tracks the size axis on the literal token (sm → sm, md → md, lg → lg); the description sits one token below it at every step, so the title/description hierarchy reads by both size and weight (semibold vs the description's normal).
Orientation
The orientation variant controls the layout direction of the toast content. Two options are available: horizontal (default) and vertical.
Horizontal
Content flows left to right in a row. Icon, text content, and dismiss button align horizontally. Best for compact, single-line notifications.
Vertical
Content stacks top to bottom in a column. Useful for toasts with longer descriptions, multiple paragraphs, or action buttons below the message.
Orientation Reference
| Orientation | Flex Direction | Use Case |
|---|---|---|
horizontal | row | Compact notifications, single-line alerts |
vertical | column | Longer descriptions, multi-line content, action buttons |
Dismissible
The useToastDismissRecipe() composable styles the close control: an em-sized button that inherits the toast's text color, pushes itself to the end of the row, and includes hover and focus-visible states. The toast recipe also collapses the element when the hidden attribute is set, so dismissing only needs to toggle hidden on the root element.
Progress
The useToastProgressRecipe() composable adds a duration indicator — a prominent, rounded bar that shrinks from full width to zero, mirroring the auto-dismiss countdown so it reads at a glance. It is pure CSS: the recipe owns the toast-progress keyframes and reads the timing from a --toast-duration custom property, so nothing has to flip a data-attribute at runtime.
The bar is a rounded pill inset from the toast's corners rather than bleeding edge-to-edge. It carries its own color axis — the same one the icon uses — and paints with currentColor at full opacity, so it renders in the semantic accent while the body stays neutral. The parent toast recipe is position: relative with overflow: hidden, which anchors the bar and keeps it clipped to the toast's rounded corners.
<div role="status" class="...toast..." style="--toast-duration: 5s">
<span class="...toast-content...">Uploading files…</span>
<div class="...toast-progress..." aria-hidden="true"></div>
</div>
Set --toast-duration to match however long the toast stays on screen; it defaults to 5s when the property is absent. Mark the line aria-hidden="true" — the countdown is decorative, and screen-reader users are served by the live-region text, not the animation.
prefers-reduced-motion, pause or hide the indicator (and the auto-dismiss timer) so motion-sensitive users are not rushed.Accessibility
- Use the right live-region role. Use
role="status"(anaria-live="polite"region) for ordinary toasts so they are announced at the next pause without interrupting the user. Reserverole="alert"(aria-live="assertive") for urgent, time-critical notifications.
<!-- Ordinary, non-urgent notification -->
<div role="status" class="...">Your changes have been saved.</div>
<!-- Urgent, time-critical notification -->
<div role="alert" class="...">Connection lost. Reconnecting…</div>
- Don't rely on color alone. Always include descriptive text and pair with an icon to reinforce meaning (WCAG 1.4.1).
<!-- Correct: icon + descriptive text + color -->
<div role="status" class="...">
<span>✓</span>
<strong>Saved:</strong> Your changes were saved.
</div>
<!-- Avoid: color alone with vague text -->
<div class="...">Done</div>
- Give users time to read. Auto-dismissing toasts should stay long enough to read, and pause on hover or focus. Provide a manual dismiss control so users are never rushed.
- Dismissible toasts. Use a
<button>witharia-label="Dismiss"for the close control. Move focus to the next logical element after dismissal. - Verify contrast ratios. The body carries neutral foreground text on a neutral surface, and the semantic accent lands on the icon and progress bar. Default tokens meet WCAG AA 4.5:1 contrast. If you override colors, verify both the body text and the accent with the WebAIM Contrast Checker — note the
lightaccent is intentionally low-contrast on a light body.
variant prop to create visual hierarchy rather than relying solely on color differences. For example, use solid for critical errors and soft or subtle for informational notices.Customization
Overriding Defaults
The useToastRecipe() composable accepts an optional second argument to override any part of the recipe configuration. Overrides are deep-merged with the defaults, so you only need to specify the properties you want to change:
import { styleframe } from 'virtual:styleframe';
import { useToastRecipe } from '@styleframe/theme';
const s = styleframe();
const toast = useToastRecipe(s, {
base: {
borderRadius: '@border-radius.lg',
},
defaultVariants: {
variant: 'soft',
size: 'md',
orientation: 'horizontal',
},
});
export default s;
color is not a variant of useToastRecipe — it lives on useToastIconRecipe and useToastProgressRecipe. To change the default accent, pass defaultVariants: { color: 'info' } to those recipes instead.Filtering Variants
If you only need a subset of the available variants, use the filter option to limit which values are generated. This reduces the output CSS and keeps your component API focused:
import { styleframe } from 'virtual:styleframe';
import { useToastRecipe } from '@styleframe/theme';
const s = styleframe();
// Only generate the soft and subtle surface styles
const toast = useToastRecipe(s, {
filter: {
variant: ['soft', 'subtle'],
},
});
export default s;
useToastIconRecipe(s, { filter: { color: ['success', 'error'] } }).solid clears the variant default.API Reference
useToastRecipe(s, options?)
Creates the toast surface recipe with variant, size, and orientation axes. The color accent lives on the icon and progress sub-recipes, not here.
Parameters:
| Parameter | Type | Description |
|---|---|---|
s | Styleframe | The Styleframe instance |
options | DeepPartial<RecipeConfig> | Optional overrides for the recipe configuration |
options.base | VariantDeclarationsBlock | Custom base styles for the toast |
options.variants | Variants | Custom variant definitions for the recipe |
options.defaultVariants | Record<keyof Variants, string> | Default variant values for the recipe |
options.compoundVariants | CompoundVariant[] | Custom compound variant definitions for the recipe |
options.filter | Record<string, string[]> | Limit which variant values are generated |
Variants:
| Variant | Options | Default |
|---|---|---|
variant | solid, soft, subtle | solid |
size | sm, md, lg | md |
orientation | horizontal, vertical | horizontal |
The color accent is a variant of the icon and progress sub-recipes:
| Variant | Options | Default |
|---|---|---|
color | primary, secondary, success, info, warning, error, light, dark, neutral | neutral |
Sub-part recipes
The toast's parts are styled by recipes that accept the same (s, options?) signature. Most are base-only; the icon and progress recipes each carry the color axis that paints the semantic accent:
| Composable | Class | Purpose |
|---|---|---|
useToastIconRecipe() | toast-icon | Non-shrinking inline-flex wrapper for the leading icon; carries the color accent |
useToastContentRecipe() | toast-content | Main text area, fills the available space |
useToastTitleRecipe() | toast-title | Headline line — semibold and snug, sits above the description |
useToastDescriptionRecipe() | toast-description | Supporting line — one font-size token below the title, a step down in weight |
useToastProgressRecipe() | toast-progress | Prominent rounded countdown bar inset from the corners; carries the color accent; shrinks over --toast-duration (default 5s) |
useToastDismissRecipe() | toast-dismiss | Dismiss button reset with hover and focus-visible states |
Best Practices
- Choose colors by meaning, not appearance: Use
successfor confirmations,errorfor failures, andwarningfor caution messages — this keeps your UI consistent when tokens change. - Use
neutralfor generic messages: The neutral color adapts to light and dark mode automatically, making it the safest default for general-purpose toasts. - Prefer
subtleorsoftfor informational toasts: Reservesolidfor critical notifications that demand immediate attention. Too many solid toasts create visual fatigue. - Pair an icon with the message: Icons reinforce the toast's meaning and improve scannability, especially for users who cannot distinguish colors.
- Use
verticalorientation for longer content: When toasts contain multi-line descriptions or action buttons, vertical layout prevents cramped horizontal spacing. - Filter what you don't need: If your application only uses a few colors, pass a
filteroption to reduce generated CSS. - Override defaults at the recipe level: Set your most common variant combination as
defaultVariantsso component consumers write less code. - Include descriptive text: Toasts should not rely on color alone to convey meaning. Always include a title or description that communicates the message.
FAQ
solid, soft, subtle — dropping callout's outline, since a transparent-background surface floating over arbitrary page content would be illegible. The other difference is presentation: the toast floats. Its base adds a soft, diffuse @box-shadow.lg elevation, well-rounded @border-radius.lg corners, and generous padding, and omits the full-width flex-basis so it sizes to its content, where the callout is an inline, full-width surface. Use a callout for inline, in-flow messages and a toast for transient, floating notifications.color accent is scoped to the icon and progress bar, so the toast body can stay a calm neutral card for every color. Keeping color off the surface means the variant axis (solid, soft, subtle) is purely a neutral surface treatment, with no color interactions to compound — so the recipe has no compound variants at all. The accent instead lives as a color axis on useToastIconRecipe and useToastProgressRecipe, both painting with currentColor so an SVG or icon-font glyph picks it up automatically.dark and neutral both resolve to the adaptive @color.text foreground (dark ink on a light card, light ink on a dark card). light uses the inverted foreground @color.text-inverted, matching the family's outline light accent — note this is intentionally low-contrast on a light body. For a general-purpose toast that blends with the surrounding interface, neutral is the safest default.hidden rule so dismissal only needs to toggle the hidden attribute.subtle also adds a hairline border, giving the toast more visual definition. Use soft when you want a gentler, borderless appearance, and subtle when the toast needs slightly more structure. Neither carries a color — the accent is on the icon and progress bar regardless of variant.useToastDismissRecipe() composable styles the close control, and the toast recipe collapses the element when the hidden attribute is set. Dismissal logic — toggling hidden, animating out, managing focus — remains the responsibility of your component framework. Use a <button> element with aria-label="Dismiss" for the close control, and move focus to the next logical element after dismissal.@color.primary, @font-size.sm, @box-shadow.lg, and @border-radius.lg through string refs. These tokens need to be defined in your Styleframe instance for the recipe to generate valid CSS. The easiest way is to use useDesignTokensPreset(s), but you can also define the required tokens manually.Spinner
A loading spinner component with color, size, and optional overlay — built as a multi-part recipe system with SVG-based animation.
Calendar
A date-grid styling recipe with selected, today, range, booked, disabled, and outside-month day states, week numbers, month/year selectors, presets and a time-picker footer, plus a custom cell size through the recipe system.