Badge
Small label for status, count, or metadata. Static — seven variants span from filled to link and a dotted indicator. Renders as a span by default; becomes an anchor when href is set.
Variants
<script lang="ts">
import { Badge } from '$lib/components/ui/badge';
</script>
<Badge>default</Badge>
<Badge variant="secondary">secondary</Badge>
<Badge variant="destructive">destructive</Badge>
<Badge variant="success">success</Badge>
<Badge variant="warning">warning</Badge>
<Badge variant="info">info</Badge>
<Badge variant="outline">outline</Badge>
<Badge variant="ghost">ghost</Badge>
<Badge variant="link">link</Badge>
<Badge variant="dot">dot</Badge>With icon
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const badgeVariants = tv({
base: 'gap-1 rounded-(--radius-control) border border-transparent font-medium has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:ring-(length:--ring-width) [&>svg]:pointer-events-none',
variants: {
variant: {
default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
secondary: 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',
destructive:
'bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20',
success:
'bg-success/10 text-success [a]:hover:bg-success/20 dark:bg-success/20 dark:text-success',
warning:
'bg-warning/10 text-warning [a]:hover:bg-warning/20 dark:bg-warning/20 dark:text-warning',
info: 'bg-info/10 text-info [a]:hover:bg-info/20 dark:bg-info/20 dark:text-info',
outline:
'border-border text-foreground [a]:hover:bg-substrate-hover [a]:hover:text-muted-foreground bg-input/30',
ghost: 'hover:bg-substrate-hover hover:text-muted-foreground',
link: 'text-primary underline-offset-4 hover:underline',
dot: 'border-border bg-input/30 text-foreground pl-1.5 before:size-1.5 before:rounded-full before:bg-current before:shrink-0 before:content-[""] before:block'
},
size: {
sm: 'h-4 px-1.5 text-[0.6875rem] [&>svg]:size-3!',
default: 'h-5 px-2 py-0.5 text-xs [&>svg]:size-3!',
lg: 'h-6 px-2.5 py-0.5 text-sm [&>svg]:size-3.5!'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
export type BadgeSize = VariantProps<typeof badgeVariants>['size'];
</script>
<script lang="ts">
import type { HTMLAnchorAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils.js';
let {
ref = $bindable(null),
href,
class: className,
variant = 'default',
size = 'default',
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
variant?: BadgeVariant;
size?: BadgeSize;
} = $props();
</script>
<svelte:element
this={href ? 'a' : 'span'}
bind:this={ref}
data-slot="badge"
{href}
class={cn(badgeVariants({ variant, size }), className)}
{...restProps}
>
{@render children?.()}
</svelte:element>
Sizes
Three heights: sm (16px) for dense table rows, default (20px) for general status, and lg (24px) for hero labels or trend pills.
<Badge size="sm">Beta</Badge>
<Badge>Active</Badge>
<Badge size="lg">Premium</Badge>Trend — price up / down
Trend pills are a composition, not a dedicated primitive — pair a semantic variant (success / destructive) with a directional icon from Remix. The
icon auto-sizes to the badge size scale.
<script lang="ts">
import { Badge } from '$lib/components/ui/badge';
import { Icon } from '$lib/components/ui/icon';
</script>
<!-- Trend is a composition, not a new primitive — semantic variant + directional icon. -->
<Badge variant="success"><Icon name="arrow-up-line" />12.4%</Badge>
<Badge variant="destructive"><Icon name="arrow-down-line" />3.1%</Badge>
<Badge variant="success" size="lg"><Icon name="arrow-up-line" />+$8,420</Badge>
<Badge variant="destructive" size="lg"><Icon name="arrow-down-line" />−$1,208</Badge>Dot status
The dot variant inherits the leading dot's color from text-current — set a status text color on the badge to tint
the indicator. Useful for connection / availability / health rows.
<Badge variant="dot" class="text-success">Online</Badge>
<Badge variant="dot" class="text-warning">Degraded</Badge>
<Badge variant="dot" class="text-destructive">Offline</Badge>
<Badge variant="dot" class="text-info">Syncing</Badge>As link
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const badgeVariants = tv({
base: 'gap-1 rounded-(--radius-control) border border-transparent font-medium has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:ring-(length:--ring-width) [&>svg]:pointer-events-none',
variants: {
variant: {
default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
secondary: 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',
destructive:
'bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20',
success:
'bg-success/10 text-success [a]:hover:bg-success/20 dark:bg-success/20 dark:text-success',
warning:
'bg-warning/10 text-warning [a]:hover:bg-warning/20 dark:bg-warning/20 dark:text-warning',
info: 'bg-info/10 text-info [a]:hover:bg-info/20 dark:bg-info/20 dark:text-info',
outline:
'border-border text-foreground [a]:hover:bg-substrate-hover [a]:hover:text-muted-foreground bg-input/30',
ghost: 'hover:bg-substrate-hover hover:text-muted-foreground',
link: 'text-primary underline-offset-4 hover:underline',
dot: 'border-border bg-input/30 text-foreground pl-1.5 before:size-1.5 before:rounded-full before:bg-current before:shrink-0 before:content-[""] before:block'
},
size: {
sm: 'h-4 px-1.5 text-[0.6875rem] [&>svg]:size-3!',
default: 'h-5 px-2 py-0.5 text-xs [&>svg]:size-3!',
lg: 'h-6 px-2.5 py-0.5 text-sm [&>svg]:size-3.5!'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
export type BadgeSize = VariantProps<typeof badgeVariants>['size'];
</script>
<script lang="ts">
import type { HTMLAnchorAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils.js';
let {
ref = $bindable(null),
href,
class: className,
variant = 'default',
size = 'default',
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
variant?: BadgeVariant;
size?: BadgeSize;
} = $props();
</script>
<svelte:element
this={href ? 'a' : 'span'}
bind:this={ref}
data-slot="badge"
{href}
class={cn(badgeVariants({ variant, size }), className)}
{...restProps}
>
{@render children?.()}
</svelte:element>
API reference
Inherits every native HTML anchor attribute via spread. Switches to <a> when href is set; renders as <span> otherwise.
| Prop | Type | Default | Description |
|---|---|---|---|
'default' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'outline' | 'ghost' | 'link' | 'dot' | 'default' | Visual style. Semantic variants (success/warning/info) pull from the status tokens. `dot` renders a leading filled circle; `link` drops the background for an inline text affordance. | |
'sm' | 'default' | 'lg' | 'default' | Height + text scale. 16px / 20px / 24px. | |
string | undefined | — | When set, the badge renders as an anchor and receives link semantics. | |
string | — | Merged onto the root via tailwind-merge. | |
HTMLElement | null | null | Two-way-bindable element reference. |