Callout
Overview
The Callout is a contextual feedback element used for alerts, notifications, status messages, and inline notices. The useCalloutRecipe() composable creates a fully configured recipe with color, variant, size, and orientation options — plus compound variants that handle the color-variant combinations automatically.
The Callout 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 Callout recipe?
The Callout recipe helps you:
- Ship faster with sensible defaults: Get 9 colors, 4 visual styles, 3 sizes, and 2 orientations out of the box with a single composable call.
- Maintain consistency: Compound variants ensure every color-variant combination follows the same design rules, including dark mode overrides.
- 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.
- Support flexible layouts: Horizontal and vertical orientations adapt to your content structure, from compact inline messages to detailed multi-line notices.
Usage
Register the recipe
Add the Callout 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 { useCalloutRecipe } from '@styleframe/theme';
const s = styleframe();
const callout = useCalloutRecipe(s);
export default s;
Build the component
Import the callout runtime function from the virtual module and pass variant props to compute class names:
import { callout } from "virtual:styleframe";
interface CalloutProps {
color?: "primary" | "secondary" | "success" | "info" | "warning" | "danger" | "light" | "dark" | "neutral";
variant?: "solid" | "outline" | "soft" | "subtle";
size?: "sm" | "md" | "lg";
orientation?: "horizontal" | "vertical";
title?: string;
description?: string;
icon?: React.ReactNode;
dismissible?: boolean;
onDismiss?: () => void;
children?: React.ReactNode;
}
export function Callout({
color = "neutral",
variant = "subtle",
size = "md",
orientation = "horizontal",
title,
description,
icon,
dismissible = false,
onDismiss,
children,
}: CalloutProps) {
const classes = callout({ color, variant, size, orientation });
return (
<div className={classes} role="alert">
{icon && <span>{icon}</span>}
<div>
{title && <strong>{title}</strong>}
{description && <p>{description}</p>}
{children}
</div>
{dismissible && (
<button onClick={onDismiss} aria-label="Dismiss">
×
</button>
)}
</div>
);
}
<script setup lang="ts">
import { callout } from "virtual:styleframe";
const {
color = "neutral",
variant = "subtle",
size = "md",
orientation = "horizontal",
title,
description,
dismissible = false,
} = defineProps<{
color?: "primary" | "secondary" | "success" | "info" | "warning" | "danger" | "light" | "dark" | "neutral";
variant?: "solid" | "outline" | "soft" | "subtle";
size?: "sm" | "md" | "lg";
orientation?: "horizontal" | "vertical";
title?: string;
description?: string;
dismissible?: boolean;
}>();
const emit = defineEmits<{
dismiss: [];
}>();
</script>
<template>
<div :class="callout({ color, variant, size, orientation })" role="alert">
<slot name="icon" />
<div>
<strong v-if="title">{{ title }}</strong>
<p v-if="description">{{ description }}</p>
<slot />
</div>
<button v-if="dismissible" aria-label="Dismiss" @click="emit('dismiss')">
×
</button>
</div>
</template>
See it in action
Colors
The Callout recipe includes 9 color variants: the 6 semantic colors (primary, secondary, success, info, warning, danger) plus 3 neutral-spectrum colors (light, dark, neutral). Each color is combined with every visual style variant through compound variants, so you get consistent, predictable styling across all combinations — including dark mode overrides.
The neutral color adapts automatically: it uses a light appearance in light mode and a dark appearance in dark mode, making it the safest default for general-purpose callouts.
Color Reference
| Color | Token | Use Case |
|---|---|---|
primary | @color.primary | Primary brand messages, 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, deprecation notices |
danger | @color.danger | Error messages, destructive warnings, critical alerts |
light | @color.white / @color.gray-* | Light-themed callouts, minimal visual weight |
dark | @color.gray-900 | Dark-themed callouts, high visual weight |
neutral | Adaptive | Default. Light appearance in light mode, dark in dark mode |
Variants
Four visual style variants control how the callout is rendered. Each variant is combined with the selected color through compound variants, so you always get the correct background, text, and border colors for your chosen color.
Solid
Filled background with light text. The most prominent style, ideal for critical alerts that demand immediate attention.
Outline
Transparent background with colored border and text. Useful for secondary messages that provide context without dominating the visual hierarchy.
Soft
Light tinted background with colored text. A gentle but visible style that works well for informational messages and tips.
Subtle
Light tinted background with colored text and a matching border. Combines the softness of the soft variant with the definition of outline. This is the default variant for the Callout recipe.
Sizes
Three size variants from sm to lg control the font size, padding, and gap of the callout.
Size Reference
| Size | Font Size | Padding (V / H) | Gap |
|---|---|---|---|
sm | @font-size.xs | @0.5 / @0.75 | @0.5 |
md | @font-size.sm | @0.75 / @1 | @0.75 |
lg | @font-size.md | @1 / @1.25 | @1 |
Orientation
The orientation variant controls the layout direction of the callout 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 messages.
Vertical
Content stacks top to bottom in a column. Useful for callouts with longer descriptions, multiple paragraphs, or action buttons below the message.
Orientation Reference
| Orientation | Flex Direction | Use Case |
|---|---|---|
horizontal | row | Compact inline messages, single-line alerts |
vertical | column | Longer descriptions, multi-line content, action buttons |
Accessibility
Callouts communicate important information to users. Follow these guidelines to ensure they are accessible to everyone.
Use appropriate ARIA roles
Choose the right role based on the callout's urgency:
role="alert"— For urgent, time-sensitive messages (errors, critical warnings). Screen readers announce these immediately.role="status"— For non-urgent updates (success confirmations, progress notifications). Announced at the next convenient pause.- No role — For static informational content (tips, notes). Use a plain
<div>or<aside>.
<!-- Urgent error message -->
<div role="alert" class="...">Payment failed. Please try again.</div>
<!-- Non-urgent status update -->
<div role="status" class="...">Your changes have been saved.</div>
<!-- Informational aside -->
<aside class="...">Tip: You can customize your dashboard in settings.</aside>
Do not rely on color alone
Callouts should never communicate meaning through color alone. Always include a descriptive title or message text. Pairing an icon with the text further reinforces the callout's severity for users who cannot distinguish colors, following WCAG 1.4.1 (Use of Color).
<!-- Correct: icon + descriptive text + color -->
<div role="alert" class="...">
<span>⚠</span>
<strong>Warning:</strong> Your session will expire in 5 minutes.
</div>
<!-- Avoid: color alone with vague text -->
<div class="...">Note</div>
Dismissible callouts
When a callout is dismissible, the dismiss button must be keyboard-accessible. Use a <button> element with aria-label="Dismiss" so screen readers can announce its purpose. After dismissal, move focus to the next logical element in the page.
Contrast ratios
The solid variant places light text (@color.white) on a colored background. All default color tokens meet the WCAG AA minimum contrast ratio of 4.5:1 for normal text. If you override the default colors, verify contrast ratios with a tool like the WebAIM Contrast Checker.
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 useCalloutRecipe() 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 { useCalloutRecipe } from '@styleframe/theme';
const s = styleframe();
const callout = useCalloutRecipe(s, {
base: {
borderRadius: '@border-radius.lg',
},
defaultVariants: {
color: 'info',
variant: 'soft',
size: 'md',
orientation: 'horizontal',
},
});
export default s;
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 { useCalloutRecipe } from '@styleframe/theme';
const s = styleframe();
// Only generate info and danger colors, with soft and subtle styles
const callout = useCalloutRecipe(s, {
filter: {
color: ['info', 'danger'],
variant: ['soft', 'subtle'],
},
});
export default s;
API Reference
useCalloutRecipe(s, options?)
Creates a full callout recipe with all variants and compound variant styling.
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 callout |
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 |
|---|---|---|
color | primary, secondary, success, info, warning, danger, light, dark, neutral | neutral |
variant | solid, outline, soft, subtle | subtle |
size | sm, md, lg | md |
orientation | horizontal, vertical | horizontal |
Best Practices
- Choose colors by meaning, not appearance: Use
successfor confirmations,dangerfor errors, 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 callouts. - Prefer
subtleorsoftfor informational callouts: Reservesolidfor critical alerts that demand immediate attention. Too many solid callouts create visual fatigue. - Pair an icon with the message: Icons reinforce the callout's severity and improve scannability, especially for users who cannot distinguish colors.
- Use
verticalorientation for longer content: When callouts 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: Callouts should not rely on color alone to convey meaning. Always include a title or description that communicates the message.
FAQ
The Callout recipe uses compound variants to map each color-variant combination to specific styles. For the 6 semantic colors, styles are generated programmatically. For the light, dark, and neutral colors, compound variants are defined individually with explicit dark mode overrides. This approach keeps the individual color and variant definitions clean while handling all 36 combinations (9 colors x 4 variants) automatically.
light always uses white and gray-100 backgrounds regardless of the color scheme. dark always uses gray-800 and gray-900 backgrounds. neutral adapts to the current color scheme: it appears light in light mode and dark in dark mode. Use neutral when you want the callout to blend naturally with the surrounding interface.Yes. Override the variants.color and compoundVariants options to add new colors. You'll need to define the empty color variant and add compound variants for each visual style:
const callout = useCalloutRecipe(s, {
variants: {
color: {
brand: {},
},
},
compoundVariants: [
{ match: { color: 'brand', variant: 'solid' }, css: { background: '@color.brand', color: '@color.white' } },
{ match: { color: 'brand', variant: 'outline' }, css: { color: '@color.brand', borderColor: '@color.brand' } },
// ... soft and subtle
],
});
subtle also adds a matching colored border, giving the callout more visual definition. Use soft when you want a gentler appearance, and subtle when the callout needs slightly more structure.horizontal (the default) for short, single-line messages where the icon, text, and dismiss button fit comfortably in a row. Switch to vertical when the callout contains longer descriptions, multiple paragraphs, or action buttons that benefit from stacking top to bottom.filter option, compound variants that reference filtered-out values are automatically removed. For example, if you filter variant to only ['solid', 'outline'], all compound variants matching soft or subtle are excluded from the generated output. Default variants are also adjusted if they reference a removed value.<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, and @border-radius.md 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.Button
An interactive control component for actions and navigation. Supports multiple colors, visual styles, sizes, and states through the recipe system.
Overview
Explore Styleframe's utility composables for generating CSS utility classes. Create flexible, reusable styling primitives with full type safety.