Skip to content
edmi-ui

Badge

Compact status label.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
IndexDevnetxStockFailedDraftSolscanLiveSettledDrift 6%RebalancingPill12
Index
Devnet
xStock
Failed
Draft
Solscan
Live
Settled
Drift 6%
Rebalancing
Pill
12
Index Devnet xStock Failed Draft Solscan Live Settled Drift 6% Rebalancing Pill 12
Terminal window
npx shadcn@latest add @edmi-ui/badge
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/badge
Terminal window
yarn dlx shadcn@latest add @edmi-ui/badge
Terminal window
bunx shadcn@latest add @edmi-ui/badge

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/badge
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/badge
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/badge
Terminal window
bunx shadcn-vue@latest add @edmi-ui/badge

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/badge.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/badge.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/badge.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/badge.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import { Badge } from "@/components/ui/badge";
<div className="flex flex-wrap items-center gap-3">
<Badge>Index</Badge>
<Badge variant="secondary">Devnet</Badge>
<Badge variant="outline">xStock</Badge>
<Badge variant="destructive">Failed</Badge>
<Badge variant="ghost">Draft</Badge>
<Badge variant="link">Solscan</Badge>
<Badge variant="brand">Live</Badge>
<Badge variant="success">Settled</Badge>
<Badge variant="warning">Drift 6%</Badge>
<Badge variant="info">Rebalancing</Badge>
<Badge shape="pill" variant="brand">
Pill
</Badge>
<Badge shape="number">12</Badge>
</div>
<script setup lang="ts">
import { Badge } from "@/components/ui/badge";
</script>
<template>
<div class="flex flex-wrap items-center gap-3">
<Badge>Index</Badge>
<Badge variant="secondary">Devnet</Badge>
<Badge variant="outline">xStock</Badge>
<Badge variant="destructive">Failed</Badge>
<Badge variant="ghost">Draft</Badge>
<Badge variant="link">Solscan</Badge>
<Badge variant="brand">Live</Badge>
<Badge variant="success">Settled</Badge>
<Badge variant="warning">Drift 6%</Badge>
<Badge variant="info">Rebalancing</Badge>
<Badge shape="pill" variant="brand">Pill</Badge>
<Badge shape="number">12</Badge>
</div>
</template>
<script lang="ts">
import { Badge } from "$lib/components/ui/badge";
</script>
<div class="flex flex-wrap items-center gap-3">
<Badge>Index</Badge>
<Badge variant="secondary">Devnet</Badge>
<Badge variant="outline">xStock</Badge>
<Badge variant="destructive">Failed</Badge>
<Badge variant="ghost">Draft</Badge>
<Badge variant="link">Solscan</Badge>
<Badge variant="brand">Live</Badge>
<Badge variant="success">Settled</Badge>
<Badge variant="warning">Drift 6%</Badge>
<Badge variant="info">Rebalancing</Badge>
<Badge shape="pill" variant="brand">Pill</Badge>
<Badge shape="number">12</Badge>
</div>
registry/ui/badge.tsx
"use client";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "cn";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
const badgeVariants = cva(
"group/badge inline-flex h-[22px] w-fit shrink-0 items-center justify-center gap-1.5 overflow-hidden rounded-md border border-transparent px-2 text-xs font-medium whitespace-nowrap transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring aria-invalid:border-destructive [&>svg]:pointer-events-none [&_svg]:size-3",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground [a]:hover:bg-[color-mix(in_srgb,var(--primary)_85%,var(--background))]",
secondary:
"border-border bg-secondary text-secondary-foreground [a]:hover:bg-accent",
destructive:
"border-[color-mix(in_srgb,var(--destructive)_30%,var(--popover))] bg-destructive-soft text-destructive-text",
outline: "border-input text-foreground [a]:hover:bg-accent",
ghost: "text-foreground hover:bg-accent",
link: "text-foreground underline underline-offset-[3px] hover:opacity-80",
// ✦ Edmi additions: soft fill + tinted border, never solid
brand:
"border-[color-mix(in_srgb,var(--brand)_30%,var(--popover))] bg-brand-soft text-brand-text",
success:
"border-[color-mix(in_srgb,var(--success)_30%,var(--popover))] bg-success-soft text-success-text",
warning:
"border-[color-mix(in_srgb,var(--warning)_30%,var(--popover))] bg-warning-soft text-warning-text",
info: "border-[color-mix(in_srgb,var(--info)_30%,var(--popover))] bg-info-soft text-info-text",
},
// ✦ Edmi addition
shape: {
default: "",
pill: "rounded-full",
number: "min-w-[22px] justify-center px-1.5 font-mono text-[11px]",
},
// ✦ depth (v4): badges keep their fill and tinted border at every level, only the edge changes
elevation: {
flat: "",
sunken: "shadow-[inset_0_1px_2px_rgb(0_0_0/0.22)]",
raised: "shadow-raised",
floating:
"shadow-[inset_0_1px_0_var(--bv-top),0_0_1.5px_var(--bv-out),0_2px_5px_rgb(0_0_0/0.14)]",
},
},
defaultVariants: {
variant: "default",
shape: "default",
elevation: "flat",
},
},
);
function Badge({
className,
variant = "default",
shape = "default",
elevation,
render,
...props
}: useRender.ComponentProps<"span"> &
Omit<VariantProps<typeof badgeVariants>, "elevation"> & {
/** ✦ depth: sunken -1, flat 0, raised +1, floating +2 (fill and tint stay). */
elevation?: Elevation;
}) {
const level = useElevation(elevation, "control");
return useRender({
defaultTagName: "span",
props: mergeProps<"span">(
{
className: cn(
badgeVariants({ variant, shape, elevation: level }),
className,
),
},
props,
),
render,
state: {
slot: "badge",
variant,
},
});
}
export { Badge, badgeVariants };
registry/ui/badge/Badge.vue
<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { BadgeVariants } from '.'
import { reactiveOmit } from '@vueuse/core'
import { Primitive } from 'reka-ui'
import { cn } from '@/registry/edmi/lib/utils'
import { type Elevation, useElevation } from '@/registry/edmi/ui/elevation'
import { badgeVariants } from '.'
const props = withDefaults(defineProps<PrimitiveProps & {
variant?: BadgeVariants['variant']
/** ✦ Edmi addition */
shape?: BadgeVariants['shape']
/** ✦ depth: sunken -1, flat 0, raised +1, floating +2 (fill and tint stay) */
elevation?: Elevation
class?: HTMLAttributes['class']
}>(), { elevation: undefined })
const delegatedProps = reactiveOmit(props, 'class', 'variant', 'shape', 'elevation')
const level = useElevation(() => props.elevation, 'control')
</script>
<template>
<Primitive
data-slot="badge"
:data-variant="variant"
:class="cn(badgeVariants({ variant, shape, elevation: level }), props.class)"
v-bind="delegatedProps"
>
<slot />
</Primitive>
</template>
registry/ui/badge/index.ts
import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
export { default as Badge } from "./Badge.vue";
export const badgeVariants = cva(
"group/badge inline-flex h-[22px] w-fit shrink-0 items-center justify-center gap-1.5 overflow-hidden rounded-md border border-transparent px-2 text-xs font-medium whitespace-nowrap transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring aria-invalid:border-destructive [&>svg]:pointer-events-none [&_svg]:size-3",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground [a]:hover:bg-[color-mix(in_srgb,var(--primary)_85%,var(--background))]",
secondary:
"border-border bg-secondary text-secondary-foreground [a]:hover:bg-accent",
destructive:
"border-[color-mix(in_srgb,var(--destructive)_30%,var(--popover))] bg-destructive-soft text-destructive-text",
outline: "border-input text-foreground [a]:hover:bg-accent",
ghost: "text-foreground hover:bg-accent",
link: "text-foreground underline underline-offset-[3px] hover:opacity-80",
// ✦ Edmi additions: soft fill + tinted border, never solid
brand:
"border-[color-mix(in_srgb,var(--brand)_30%,var(--popover))] bg-brand-soft text-brand-text",
success:
"border-[color-mix(in_srgb,var(--success)_30%,var(--popover))] bg-success-soft text-success-text",
warning:
"border-[color-mix(in_srgb,var(--warning)_30%,var(--popover))] bg-warning-soft text-warning-text",
info: "border-[color-mix(in_srgb,var(--info)_30%,var(--popover))] bg-info-soft text-info-text",
},
// ✦ Edmi addition
shape: {
default: "",
pill: "rounded-full",
number: "min-w-[22px] justify-center px-1.5 font-mono text-[11px]",
},
// ✦ depth (v4): badges keep their fill and tinted border at every level, only the edge changes
elevation: {
flat: "",
sunken: "shadow-[inset_0_1px_2px_rgb(0_0_0/0.22)]",
raised: "shadow-raised",
floating:
"shadow-[inset_0_1px_0_var(--bv-top),0_0_1.5px_var(--bv-out),0_2px_5px_rgb(0_0_0/0.14)]",
},
},
defaultVariants: {
variant: "default",
shape: "default",
elevation: "flat",
},
},
);
export type BadgeVariants = VariantProps<typeof badgeVariants>;
src/lib/registry/ui/badge/badge.svelte
<script lang="ts" module>
import { type VariantProps, tv } from "tailwind-variants";
export const badgeVariants = tv({
base: "group/badge inline-flex h-[22px] w-fit shrink-0 items-center justify-center gap-1.5 overflow-hidden rounded-md border border-transparent px-2 text-xs font-medium whitespace-nowrap transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring aria-invalid:border-destructive [&>svg]:pointer-events-none [&_svg]:size-3",
variants: {
variant: {
default: "bg-primary text-primary-foreground [a]:hover:bg-[color-mix(in_srgb,var(--primary)_85%,var(--background))]",
secondary: "border-border bg-secondary text-secondary-foreground [a]:hover:bg-accent",
destructive: "border-[color-mix(in_srgb,var(--destructive)_30%,var(--popover))] bg-destructive-soft text-destructive-text",
outline: "border-input text-foreground [a]:hover:bg-accent",
ghost: "text-foreground hover:bg-accent",
link: "text-foreground underline underline-offset-[3px] hover:opacity-80",
// ✦ Edmi additions: soft fill + tinted border, never solid
brand: "border-[color-mix(in_srgb,var(--brand)_30%,var(--popover))] bg-brand-soft text-brand-text",
success: "border-[color-mix(in_srgb,var(--success)_30%,var(--popover))] bg-success-soft text-success-text",
warning: "border-[color-mix(in_srgb,var(--warning)_30%,var(--popover))] bg-warning-soft text-warning-text",
info: "border-[color-mix(in_srgb,var(--info)_30%,var(--popover))] bg-info-soft text-info-text",
},
// ✦ Edmi addition
shape: {
default: "",
pill: "rounded-full",
number: "min-w-[22px] justify-center px-1.5 font-mono text-[11px]",
},
// ✦ depth (v4): badges keep their fill and tinted border at every level, only the edge changes
elevation: {
flat: "",
sunken: "shadow-[inset_0_1px_2px_rgb(0_0_0/0.22)]",
raised: "shadow-raised",
floating: "shadow-[inset_0_1px_0_var(--bv-top),0_0_1.5px_var(--bv-out),0_2px_5px_rgb(0_0_0/0.14)]",
},
},
defaultVariants: {
variant: "default",
shape: "default",
elevation: "flat",
},
});
export type BadgeShape = VariantProps<typeof badgeVariants>["shape"];
export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
</script>
<script lang="ts">
import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js";
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAnchorAttributes } from "svelte/elements";
let {
ref = $bindable(null),
href,
class: className,
variant = "default",
shape = "default",
elevation = "auto",
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
variant?: BadgeVariant;
shape?: BadgeShape;
/** ✦ depth: sunken -1, flat 0, raised +1, floating +2 (fill and tint stay). */
elevation?: Elevation;
} = $props();
const level = useElevation(() => elevation, "control");
</script>
<svelte:element
this={href ? "a" : "span"}
bind:this={ref}
data-slot="badge"
{href}
class={cn(badgeVariants({ variant, shape, elevation: level.current }), className)}
{...restProps}
>
{@render children?.()}
</svelte:element>
src/lib/registry/ui/badge/index.ts
export {
type BadgeShape,
type BadgeVariant,
badgeVariants,
default as Badge,
} from "./badge.svelte";

See the Elevation guide for the levels, roles and layered mode.

Badges keep their fill and tinted border at every level; only the edge changes: sunken (-1) is a soft inset, raised (+1) a bevel, floating (+2) a bevel with a small drop.

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Sunken (-1)

defaultsecondarybrandsuccessdestructiveoutlinepill

Flat (0)

defaultsecondarybrandsuccessdestructiveoutlinepill

Raised (+1)

defaultsecondarybrandsuccessdestructiveoutlinepill

Floating (+2)

defaultsecondarybrandsuccessdestructiveoutlinepill

Sunken (-1)

default
secondary
brand
success
destructive
outline
pill

Flat (0)

default
secondary
brand
success
destructive
outline
pill

Raised (+1)

default
secondary
brand
success
destructive
outline
pill

Floating (+2)

default
secondary
brand
success
destructive
outline
pill

Sunken (-1)

defaultsecondarybrandsuccessdestructiveoutline pill

Flat (0)

defaultsecondarybrandsuccessdestructiveoutline pill

Raised (+1)

defaultsecondarybrandsuccessdestructiveoutline pill

Floating (+2)

defaultsecondarybrandsuccessdestructiveoutline pill
Prop Type Default
variant default | secondary | destructive | outline | ghost | link | brand | success | warning | info default
shape default | pill | number default
render ReactElement none
elevation "auto" | "sunken" | "flat" | "raised" | "floating" "auto"
  • brand, success, warning, info variants: soft fill with a tinted border. Use success for positive deltas and done states (green in every theme); brand follows the theme accent.
  • shape: pill and number (mono, for counts).