Styleframe Logo
Feedback

Toast

A transient, floating feedback component for notifications and status messages. Supports multiple colors, visual styles, sizes, and orientations through the recipe system.

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.lg elevation and well-rounded @border-radius.lg corners 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:

src/components/toast.styleframe.ts
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:

src/components/Toast.tsx
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">
                    &times;
                </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):

ColorAccent tokenUse Case
primary@color.primaryPrimary brand notifications, key highlights
secondary@color.secondarySecondary information, supporting messages
success@color.successPositive feedback, completions, confirmations
info@color.infoInformational messages, tips, notices
warning@color.warningCaution messages, pending states
error@color.errorError 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
Pro tip: Use semantic color names that describe purpose, not appearance. This makes it easier to update your palette without touching component code.

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

SizeTitleDescriptionPadding (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 (smsm, mdmd, lglg); 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

OrientationFlex DirectionUse Case
horizontalrowCompact notifications, single-line alerts
verticalcolumnLonger 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.

Reduced motion: the bar animates continuously toward dismissal. If you honor 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" (an aria-live="polite" region) for ordinary toasts so they are announced at the next pause without interrupting the user. Reserve role="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>&#10003;</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> with aria-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 light accent is intentionally low-contrast on a light body.
Good practice: When several toasts stack at once, use the 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:

src/components/toast.styleframe.ts
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;
Note: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:

src/components/toast.styleframe.ts
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;
Note: To limit which accent colors are generated, filter the icon and progress recipes — e.g. useToastIconRecipe(s, { filter: { color: ['success', 'error'] } }).
Good to know: When filtering removes a value that a default points at, the default adjusts to keep your recipe consistent — e.g. filtering out 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:

ParameterTypeDescription
sStyleframeThe Styleframe instance
optionsDeepPartial<RecipeConfig>Optional overrides for the recipe configuration
options.baseVariantDeclarationsBlockCustom base styles for the toast
options.variantsVariantsCustom variant definitions for the recipe
options.defaultVariantsRecord<keyof Variants, string>Default variant values for the recipe
options.compoundVariantsCompoundVariant[]Custom compound variant definitions for the recipe
options.filterRecord<string, string[]>Limit which variant values are generated

Variants:

VariantOptionsDefault
variantsolid, soft, subtlesolid
sizesm, md, lgmd
orientationhorizontal, verticalhorizontal

The color accent is a variant of the icon and progress sub-recipes:

VariantOptionsDefault
colorprimary, secondary, success, info, warning, error, light, dark, neutralneutral

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:

ComposableClassPurpose
useToastIconRecipe()toast-iconNon-shrinking inline-flex wrapper for the leading icon; carries the color accent
useToastContentRecipe()toast-contentMain text area, fills the available space
useToastTitleRecipe()toast-titleHeadline line — semibold and snug, sits above the description
useToastDescriptionRecipe()toast-descriptionSupporting line — one font-size token below the title, a step down in weight
useToastProgressRecipe()toast-progressProminent rounded countdown bar inset from the corners; carries the color accent; shrinks over --toast-duration (default 5s)
useToastDismissRecipe()toast-dismissDismiss button reset with hover and focus-visible states

Learn more about recipes →

Best Practices

  • Choose colors by meaning, not appearance: Use success for confirmations, error for failures, and warning for caution messages — this keeps your UI consistent when tokens change.
  • Use neutral for generic messages: The neutral color adapts to light and dark mode automatically, making it the safest default for general-purpose toasts.
  • Prefer subtle or soft for informational toasts: Reserve solid for 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 vertical orientation 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 filter option to reduce generated CSS.
  • Override defaults at the recipe level: Set your most common variant combination as defaultVariants so 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