whiskeyjack
uibadgesource: ok · hit
Badge
Tone-tinted pill label on the semantic color scales, for status and classification at a glance.
npx shadcn add "https://whiskeyjack.net/r/badge.json"free
Where it goes
stats
Interaction
animated-counter
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightclass-variance-authority
Registry dependencies
https://whiskeyjack.net/r/utils.json
Files1 · source cached 10/8/2026, 1:51:16 PM
components/ui/badge.tsx
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const badgeVariants = cva(
'inline-flex items-center gap-1 whitespace-nowrap rounded-full px-2.5 py-1 text-xs font-medium',
{
variants: {
tone: {
neutral: '',
success: '',
info: '',
warning: '',
error: '',
orange: '',
yellow: '',
},
strong: {
true: '',
false: '',
},
},
/* The tone recipes live here because `strong` swaps the whole recipe:
soft = -100 bg / -700 text (dark: 30% -900 mix / -400), strong = one
step deeper on every channel, for the top tier of a classification
scale (e.g. a blood-pressure crisis). */
compoundVariants: [
{
tone: 'neutral',
strong: false,
className:
'bg-[var(--color-surface-muted-light)] dark:bg-[var(--color-surface-muted-dark)] text-[var(--color-text-secondary-light)] dark:text-[var(--color-text-secondary-dark)]',
},
{
tone: 'neutral',
strong: true,
className:
'bg-[var(--color-warm-200)] text-[var(--color-text-primary-light)] dark:bg-[var(--color-neutral-700)] dark:text-[var(--color-text-primary-dark)]',
},
{
tone: 'success',
strong: false,
className:
'bg-[var(--color-success-100)] text-[var(--color-success-700)] dark:bg-[color-mix(in_srgb,var(--color-success-900)_30%,transparent)] dark:text-[var(--color-success-400)]',
},
{
tone: 'success',
strong: true,
className:
'bg-[var(--color-success-200)] text-[var(--color-success-800)] dark:bg-[color-mix(in_srgb,var(--color-success-900)_50%,transparent)] dark:text-[var(--color-success-300)]',
},
{
tone: 'info',
strong: false,
className:
'bg-[var(--color-info-100)] text-[var(--color-info-700)] dark:bg-[color-mix(in_srgb,var(--color-info-900)_30%,transparent)] dark:text-[var(--color-info-400)]',
},
{
tone: 'info',
strong: true,
className:
'bg-[var(--color-info-200)] text-[var(--color-info-800)] dark:bg-[color-mix(in_srgb,var(--color-info-900)_50%,transparent)] dark:text-[var(--color-info-300)]',
},
{
tone: 'warning',
strong: false,
className:
'bg-[var(--color-warning-100)] text-[var(--color-warning-700)] dark:bg-[color-mix(in_srgb,var(--color-warning-900)_30%,transparent)] dark:text-[var(--color-warning-400)]',
},
{
tone: 'warning',
strong: true,
className:
'bg-[var(--color-warning-200)] text-[var(--color-warning-800)] dark:bg-[color-mix(in_srgb,var(--color-warning-900)_50%,transparent)] dark:text-[var(--color-warning-300)]',
},
{
tone: 'error',
strong: false,
className:
'bg-[var(--color-error-100)] text-[var(--color-error-700)] dark:bg-[color-mix(in_srgb,var(--color-error-900)_30%,transparent)] dark:text-[var(--color-error-400)]',
},
{
tone: 'error',
strong: true,
className:
'bg-[var(--color-error-200)] text-[var(--color-error-800)] dark:bg-[color-mix(in_srgb,var(--color-error-900)_50%,transparent)] dark:text-[var(--color-error-300)]',
},
{
tone: 'orange',
strong: false,
className:
'bg-[var(--color-orange-100)] text-[var(--color-orange-700)] dark:bg-[color-mix(in_srgb,var(--color-orange-900)_30%,transparent)] dark:text-[var(--color-orange-400)]',
},
{
tone: 'orange',
strong: true,
className:
'bg-[var(--color-orange-200)] text-[var(--color-orange-800)] dark:bg-[color-mix(in_srgb,var(--color-orange-900)_50%,transparent)] dark:text-[var(--color-orange-300)]',
},
{
tone: 'yellow',
strong: false,
className:
'bg-[var(--color-yellow-100)] text-[var(--color-yellow-700)] dark:bg-[color-mix(in_srgb,var(--color-yellow-900)_30%,transparent)] dark:text-[var(--color-yellow-400)]',
},
{
tone: 'yellow',
strong: true,
className:
'bg-[var(--color-yellow-200)] text-[var(--color-yellow-800)] dark:bg-[color-mix(in_srgb,var(--color-yellow-900)_50%,transparent)] dark:text-[var(--color-yellow-300)]',
},
],
defaultVariants: {
tone: 'neutral',
strong: false,
},
},
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLSpanElement>,
VariantProps<typeof badgeVariants> {}
/**
* Tone-tinted pill label on the semantic color scales – the smallest member
* of the tone family (Notice is the in-flow box, Toast the floating pill).
* For classifications and statuses read at a glance: a blood-pressure
* category, a roadmap state, a sync status. `strong` deepens the tint one
* step for the top tier of a scale (crisis / very-high readings).
*
* Non-interactive and i18n-agnostic: pass translated text as children. The
* tint never carries meaning alone – the label IS the information, the tone
* reinforces it.
*/
const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
({ tone, strong, className, ...props }, ref) => (
<span ref={ref} className={cn(badgeVariants({ tone, strong }), className)} {...props} />
),
)
Badge.displayName = 'Badge'
export { Badge, badgeVariants }