Badge
Overview
The Badge is a compact UI element used for status indicators, counts, labels, and categorization. The useBadgeRecipe() composable creates a fully configured recipe with color, variant, and size options — plus compound variants that handle the color-variant combinations automatically.
The Badge 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 Badge recipe?
The Badge recipe helps you:
- Ship faster with sensible defaults: Get 6 colors, 4 visual styles, and 5 sizes out of the box with a single composable call.
- Maintain consistency: Compound variants ensure every color-variant combination follows the same design rules.
- 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, or size 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 Badge 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 { useBadgeRecipe } from '@styleframe/theme';
const s = styleframe();
const badge = useBadgeRecipe(s);
export default s;
Build the component
Import the badge runtime function from the virtual module and pass variant props to compute class names:
import { badge } from "virtual:styleframe";
interface BadgeProps {
color?: "primary" | "secondary" | "success" | "info" | "warning" | "danger";
variant?: "solid" | "outline" | "soft" | "subtle";
size?: "xs" | "sm" | "md" | "lg" | "xl";
children?: React.ReactNode;
}
export function Badge({
color = "primary",
variant = "solid",
size = "sm",
children,
}: BadgeProps) {
const classes = badge({ color, variant, size });
return <span className={classes}>{children}</span>;
}
<script setup lang="ts">
import { badge } from "virtual:styleframe";
const { color = "primary", variant = "solid", size = "sm" } = defineProps<{
color?: "primary" | "secondary" | "success" | "info" | "warning" | "danger";
variant?: "solid" | "outline" | "soft" | "subtle";
size?: "xs" | "sm" | "md" | "lg" | "xl";
}>();
</script>
<template>
<span :class="badge({ color, variant, size })">
<slot />
</span>
</template>
See it in action
Colors
The Badge recipe includes 6 semantic color variants: primary, secondary, success, info, warning, and danger. Each color is combined with every visual style variant through compound variants, so you get consistent, predictable styling across all combinations.
Color Reference
| Color | Token | Use Case |
|---|---|---|
primary | @color.primary | Default actions, links, key information |
secondary | @color.secondary | Secondary information, neutral states |
success | @color.success | Positive states, confirmations, completions |
info | @color.info | Informational messages, tips, highlights |
warning | @color.warning | Caution states, pending actions |
danger | @color.danger | Error states, destructive actions, alerts |
Variants
Four visual style variants control how the badge 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 primary actions and key status indicators.
Outline
Transparent background with colored border and text. Useful for secondary indicators that shouldn't dominate the visual hierarchy.
Soft
Light tinted background with colored text. A subtle but visible style that works well for categorization and tags.
Subtle
Light tinted background with colored text and a matching border. Combines the softness of the soft variant with the definition of outline.
Sizes
Five size variants from xs to xl control the font size, padding, gap, and border radius of the badge.
Size Reference
| Size | Font Size | Border Radius |
|---|---|---|
xs | @font-size.2xs | @border-radius.sm |
sm | @font-size.xs | @border-radius.md |
md | @font-size.sm | @border-radius.md |
lg | @font-size.md | @border-radius.md |
xl | @font-size.lg | @border-radius.lg |
Accessibility
Badges often communicate status or category through color alone. To meet WCAG 1.4.1 (Use of Color), color must not be the only visual means of conveying information.
Pair with text labels
Always ensure badges carry a descriptive text label. If a badge uses color to indicate status (e.g., green for success, red for danger), pair it with a label like "Active" or "Error" so the meaning is clear without relying on color perception.
<!-- Correct: color + descriptive text label -->
<Badge color="success" variant="soft">Active</Badge>
<Badge color="danger" variant="soft">Error</Badge>
<!-- Avoid: color alone with ambiguous text -->
<Badge color="success" variant="solid">3</Badge>
Contrast ratios
The solid variant places light text (@color.light) 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 the primary status and soft or outline for secondary information.Customization
Overriding Defaults
The useBadgeRecipe() 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 { useBadgeRecipe } from '@styleframe/theme';
const s = styleframe();
const badge = useBadgeRecipe(s, {
base: {
borderRadius: '@border-radius.full',
},
defaultVariants: {
color: 'success',
variant: 'soft',
size: 'md',
},
});
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 { useBadgeRecipe } from '@styleframe/theme';
const s = styleframe();
// Only generate primary and danger colors, with solid and outline styles
const badge = useBadgeRecipe(s, {
filter: {
color: ['primary', 'danger'],
variant: ['solid', 'outline'],
},
});
export default s;
API Reference
useBadgeRecipe(s, options?)
Creates a full badge 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 badge |
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 | primary |
variant | solid, outline, soft, subtle | solid |
size | xs, sm, md, lg, xl | sm |
Best Practices
- Choose colors by meaning, not appearance: Use
successfor positive states anddangerfor errors — this keeps your UI consistent when tokens change. - Stick to one or two sizes per context: Mixing too many sizes in the same area creates visual noise. Pick a default size and use alternatives sparingly.
- Prefer
softorsubtlefor dense layouts: Solid badges can be overwhelming when there are many on screen. Reservesolidfor high-importance items. - Filter what you don't need: If your component 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. - Pair color with text: Badges should not rely on color alone to convey meaning. Always include a descriptive label so the information is accessible to users who cannot distinguish colors.
FAQ
The Badge recipe uses compound variants to map each color-variant combination to specific styles. For example, when color is primary and variant is solid, the compound variant applies background: @color.primary and color: @color.light. This approach keeps the individual color and variant definitions clean while handling all 24 combinations (6 colors x 4 variants) automatically.
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 badge = useBadgeRecipe(s, {
variants: {
color: {
brand: {},
},
},
compoundVariants: [
{ match: { color: 'brand', variant: 'solid' }, css: { background: '@color.brand', color: '@color.light' } },
{ match: { color: 'brand', variant: 'outline' }, css: { color: '@color.brand', borderColor: '@color.brand' } },
// ... soft and subtle
],
});
subtle also adds a matching colored border, giving the badge more visual definition. Use soft when you want a gentler appearance, and subtle when the badge needs slightly more structure.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.solid variant colors meet the WCAG AA 4.5:1 contrast ratio for normal text against @color.light. If you customize colors, verify contrast using a tool like WebAIM Contrast Checker.@color.primary, @font-size.xs, and @spacing.sm 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.