Severity Badge

Operational severity label with tone variants and optional pulse for critical incidents.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/severity-badge.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "../../lib/utils"; export type SeverityBadgeLevel = | "critical" | "high" | "info" | "low" | "medium"; const severityBadgeVariants = cva( "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { compoundVariants: [ { className: "border-transparent bg-destructive text-destructive-foreground", level: "critical", tone: "solid", }, { className: "border-destructive/30 bg-destructive/10 text-destructive", level: "critical", tone: "soft", }, { className: "border-destructive/40 text-destructive", level: "critical", tone: "outline", }, { className: "border-transparent bg-orange-500 text-white", level: "high", tone: "solid", }, { className: "border-orange-500/30 bg-orange-500/10 text-orange-600", level: "high", tone: "soft", }, { className: "border-orange-500/40 text-orange-600", level: "high", tone: "outline", }, { className: "border-transparent bg-amber-500 text-white", level: "medium", tone: "solid", }, { className: "border-amber-500/30 bg-amber-500/10 text-amber-600", level: "medium", tone: "soft", }, { className: "border-amber-500/40 text-amber-600", level: "medium", tone: "outline", }, { className: "border-transparent bg-sky-500 text-white", level: "low", tone: "solid", }, { className: "border-sky-500/30 bg-sky-500/10 text-sky-600", level: "low", tone: "soft", }, { className: "border-sky-500/40 text-sky-600", level: "low", tone: "outline", }, { className: "border-transparent bg-muted text-muted-foreground", level: "info", tone: "solid", }, { className: "border-border bg-muted/50 text-muted-foreground", level: "info", tone: "soft", }, { className: "border-border text-muted-foreground", level: "info", tone: "outline", }, ], defaultVariants: { level: "info", tone: "soft", }, variants: { level: { critical: "", high: "", info: "", low: "", medium: "", }, tone: { outline: "", soft: "", solid: "", }, }, }, ); const DOT_COLOR: Record<SeverityBadgeLevel, string> = { critical: "bg-destructive", high: "bg-orange-500", info: "bg-muted-foreground", low: "bg-sky-500", medium: "bg-amber-500", }; const DEFAULT_LABEL: Record<SeverityBadgeLevel, string> = { critical: "Critical", high: "High", info: "Info", low: "Low", medium: "Medium", }; export type SeverityBadgeProps = Omit< React.HTMLAttributes<HTMLSpanElement>, "children" > & VariantProps<typeof severityBadgeVariants> & { children?: React.ReactNode; level: SeverityBadgeLevel; pulse?: boolean; showDot?: boolean; }; function SeverityBadge({ children, className, level, pulse = false, showDot = true, tone, ...props }: SeverityBadgeProps) { const label = children ?? DEFAULT_LABEL[level]; return ( <span className={cn(severityBadgeVariants({ level, tone }), className)} data-level={level} {...props} > {showDot ? ( <span aria-hidden="true" className="relative flex size-2"> {pulse ? ( <span className={cn( "absolute inline-flex h-full w-full animate-ping rounded-full opacity-60", DOT_COLOR[level], )} /> ) : null} <span className={cn( "relative inline-flex size-2 rounded-full", DOT_COLOR[level], )} /> </span> ) : null} <span>{label}</span> </span> ); } export { SeverityBadge, severityBadgeVariants };

Dependencies

  • @vllnt/ui@^0.2.1