Installation
Install the dependencies
pnpm add cn class-variance-authorityCopy the component
Copy the following files into src/lib/components/ui/badge.
<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}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> };// 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>;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]>;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>Link
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>