Displays a badge or a component that looks like a badge.

Variants
DefaultSecondaryDestructiveOutlineGhostLink
Icon Left
Default Secondary Destructive Outline Ghost Link
Icon Right
Default Secondary Destructive Outline Ghost Link
With Spinner
Default Secondary Destructive Outline Ghost Link
Long Text
A badge with a lot of text to see how it wraps
Custom Colors
BlueGreenSkyPurpleBlueGreenSkyPurpleRed

Installation

Install the dependencies

pnpm add cn class-variance-authority

Copy the component

Copy the following files into src/lib/components/ui/badge.

src/lib/components/ui/badge/Badge.svelte
<script lang="ts">
  import type { BadgeProps, BadgeState } from './types.js';
  import { cn } from 'cn';
  import { badgeVariants } from './variants.js';
  let { children, class: classProp, variant = 'default', render, ...props }: BadgeProps = $props();
  const state: BadgeState = $derived({ slot: 'badge', variant });
  const hostProps = $derived({ 'data-slot': 'badge', 'data-variant': state.variant, ...props, class: cn(badgeVariants({ variant }), classProp) });
</script>
{#if render}
  {@render render(hostProps, state, children)}
{:else}
  <span {...hostProps}>{@render children?.()}</span>
{/if}
src/lib/components/ui/badge/types.ts
import type { HTMLAttributes } from 'svelte/elements';
import type { RenderSnippet } from '../shared/render.js';
import type { BadgeVariants } from './variants.js';
export type BadgeState = { slot: 'badge'; variant: BadgeVariants['variant'] };
export type BadgeProps = HTMLAttributes<HTMLSpanElement> & BadgeVariants & { render?: RenderSnippet<BadgeState, HTMLSpanElement> };
src/lib/components/ui/badge/variants.ts
// Adapted from shadcn-ui/ui d75a96ab; MIT: packages/ui/THIRD_PARTY_NOTICES.md.
import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
export const badgeVariants = cva('h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>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', outline: 'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',
      ghost: 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50', link: 'text-primary underline-offset-4 hover:underline',
    },
  },
  defaultVariants: { variant: 'default' },
});
export type BadgeVariants = VariantProps<typeof badgeVariants>;
src/lib/components/ui/shared/render.ts
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';

/**
 * Replaces a part's default element, like the `render` snippets of Sveltery Base.
 * Spread `props` onto the host element and render `children` inside it.
 * `Host` is the element the part renders by default, so its typed event handlers carry over.
 */
export type RenderSnippet<State, Host extends HTMLElement = HTMLElement> = Snippet<[props: HTMLAttributes<Host>, state: State, children: Snippet | undefined]>;
src/lib/components/ui/badge/index.ts
export { default as Badge } from './Badge.svelte';
export { badgeVariants } from './variants.js';
export type { BadgeVariants } from './variants.js';
export type { BadgeProps, BadgeState } from './types.js';

Update the import paths

Change the imports to match your project setup.

Usage

<script lang="ts">
  import { Badge } from '$lib/components/ui/badge';
</script>
<Badge variant="outline">Badge</Badge>

Pass a render snippet to render the badge as another element. Spread props onto it and render children inside it.

<Badge>
  {#snippet render(props, state, children)}
    <a href="/changelog" {...props}>{@render children?.()}</a>
  {/snippet}
  New
</Badge>