Composables

Badge

A compact labeling component for status indicators, counts, and categorization. Supports multiple colors, visual styles, and sizes through the recipe system.

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:

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

src/components/Badge.tsx
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>;
}

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

ColorTokenUse Case
primary@color.primaryDefault actions, links, key information
secondary@color.secondarySecondary information, neutral states
success@color.successPositive states, confirmations, completions
info@color.infoInformational messages, tips, highlights
warning@color.warningCaution states, pending actions
danger@color.dangerError states, destructive actions, alerts
Pro tip: Use semantic color names that describe purpose, not appearance. This makes it easier to update your palette without touching component code.

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

SizeFont SizeBorder 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.

Good practice: When badges appear next to each other in a list or table, use the 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:

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

src/components/badge.styleframe.ts
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;
Good to know: Filtering also removes compound variants and adjusts default variants that reference filtered-out values, so your recipe stays consistent.

API Reference

useBadgeRecipe(s, options?)

Creates a full badge recipe with all variants and compound variant styling.

Parameters:

ParameterTypeDescription
sStyleframeThe Styleframe instance
optionsDeepPartial<RecipeConfig>Optional overrides for the recipe configuration
options.baseVariantDeclarationsBlockCustom base styles for the badge
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
colorprimary, secondary, success, info, warning, dangerprimary
variantsolid, outline, soft, subtlesolid
sizexs, sm, md, lg, xlsm

Learn more about recipes →

Best Practices

  • Choose colors by meaning, not appearance: Use success for positive states and danger for 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 soft or subtle for dense layouts: Solid badges can be overwhelming when there are many on screen. Reserve solid for high-importance items.
  • Filter what you don't need: If your component 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.
  • 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