Badge
Displays a badge or a component that looks like a badge.
npx shadcn@latest add @velaui/badgepnpm dlx velaui add @velaui/badgeyarn dlx shadcn@latest add @velaui/badgebunx --bun shadcn@latest add @velaui/badgeveni add badgeInstalls the copy of vela-ui that veni-builder serves to Venizia apps; veni-builder serves the Radix UI copy only, so this tab exists for that base alone.
npx shadcn@latest add @velaui/badgepnpm dlx velaui add @velaui/badgeyarn dlx shadcn@latest add @velaui/badgebunx --bun shadcn@latest add @velaui/badgeExamples
- Default
- Count
- Verified
- Expiring
- In progress
- Draft
- Docs link
- Blocked
- Changelog
- Failed
Usage
Props
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
color | "info" | "success" | "warning" | "destructive" | "neutral" | "inverse" | "accent" | "neutral" |
dot | boolean | false |
size | "sm" | "md" | "lg" | "md" |
variant | "outline" | "solid" | "subtle" | "ghost" | "solid" |
Takes these and every prop of BadgeProps.
| Prop | Type | Default |
|---|---|---|
color | "info" | "success" | "warning" | "destructive" | "neutral" | "inverse" | "accent" | "neutral" |
dot | boolean | false |
size | "sm" | "md" | "lg" | "md" |
variant | "outline" | "solid" | "subtle" | "ghost" | "solid" |
Takes these and every prop of BadgeProps.
Dependencies
- npm
class-variance-authority@0.7.1radix-ui@1.6.0- Installs with it
- nothing else
- npm
@base-ui/react@1.6.0class-variance-authority@0.7.1- Installs with it
- nothing else
Source
components/ui/badge.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "radix-ui";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"group/badge inline-flex w-fit shrink-0 items-center justify-center rounded-full border border-transparent font-medium whitespace-nowrap transition-all focus-visible:border-ring-neutral-outline focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow [&>svg]:pointer-events-none [&>svg]:shrink-0",
{
variants: {
variant: {
solid: "",
subtle: "",
outline: "",
ghost: "",
},
color: {
neutral: "",
inverse: "",
info: "",
success: "",
warning: "",
destructive: "",
accent: "",
},
size: {
sm: "h-4 gap-0.5 px-1.5 py-0.5 text-[10px] leading-[1.3] has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 [&>svg]:size-3",
md: "h-5 gap-1 px-2 py-1 text-xs leading-[1.3] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3.5",
lg: "h-6 gap-1 px-2 py-1 text-sm leading-[1.4] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-4",
},
},
compoundVariants: [
// Solid — filled surface in the colour, label inverted against it
{
variant: "solid",
color: "neutral",
className: "bg-bg-foreground text-text-inverse",
},
{
variant: "solid",
color: "inverse",
className: "bg-bg-main text-text-strong",
},
{
variant: "solid",
color: "info",
className: "bg-info-middle text-base-white",
},
{
variant: "solid",
color: "success",
className: "bg-success-middle text-base-white",
},
{
variant: "solid",
color: "warning",
className: "bg-warning-middle text-base-white",
},
{
variant: "solid",
color: "destructive",
className: "bg-negative-middle text-base-white",
},
{
variant: "solid",
color: "accent",
className: "bg-accent-default text-base-white",
},
// Subtle — tinted surface, label in the colour itself
{
variant: "subtle",
color: "neutral",
className: "bg-bg-surface-1 text-text-strong",
},
{
variant: "subtle",
color: "inverse",
className: "bg-bg-surface-inverse text-text-inverse",
},
{
variant: "subtle",
color: "info",
className: "bg-info text-info-strong",
},
{
variant: "subtle",
color: "success",
className: "bg-success text-success-middle",
},
{
variant: "subtle",
color: "warning",
className: "bg-warning text-warning-middle",
},
{
variant: "subtle",
color: "destructive",
className: "bg-negative text-negative-middle",
},
{
variant: "subtle",
color: "accent",
className: "bg-teal-50 text-accent-default",
},
// Outline — no surface, 1px border and label in the colour
{
variant: "outline",
color: "neutral",
className: "border-border-default text-text-strong",
},
{
variant: "outline",
color: "inverse",
className: "border-border-inverse text-text-inverse",
},
{
variant: "outline",
color: "info",
className: "border-info-middle text-info-middle",
},
{
variant: "outline",
color: "success",
className: "border-success-middle text-success-middle",
},
{
variant: "outline",
color: "warning",
className: "border-warning-middle text-warning-middle",
},
{
variant: "outline",
color: "destructive",
className: "border-negative-middle text-negative-middle",
},
{
variant: "outline",
color: "accent",
className: "border-accent-default text-accent-default",
},
// Ghost — transparent surface with the neutral label colour; hover fills
// in with the matching subtle surface.
{
variant: "ghost",
color: "neutral",
className: "text-text-strong hover:bg-bg-surface-1",
},
{
variant: "ghost",
color: "inverse",
className: "text-text-inverse hover:bg-bg-surface-inverse",
},
{
variant: "ghost",
color: "info",
className: "text-text-strong hover:bg-info",
},
{
variant: "ghost",
color: "success",
className: "text-text-strong hover:bg-success",
},
{
variant: "ghost",
color: "warning",
className: "text-text-strong hover:bg-warning",
},
{
variant: "ghost",
color: "destructive",
className: "text-text-strong hover:bg-negative",
},
{
variant: "ghost",
color: "accent",
className: "text-accent-default hover:bg-teal-50",
},
{ variant: "ghost", className: "bg-transparent" },
],
defaultVariants: {
variant: "solid",
color: "neutral",
size: "md",
},
},
);
// The dot sits in a slightly larger box so it keeps a small optical gap from
// its neighbours. It takes the badge's own label colour.
const badgeDotVariants = cva(
"flex shrink-0 items-center justify-center *:rounded-full *:bg-current",
{
variants: {
size: {
sm: "size-[9px] *:size-1.5",
md: "size-[11px] *:size-[7px]",
lg: "size-[14px] *:size-[9px]",
},
},
defaultVariants: {
size: "md",
},
},
);
export type BadgeProps = React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { dot?: boolean; asChild?: boolean };
function Badge({
className,
variant = "solid",
color = "neutral",
size = "md",
dot = false,
asChild = false,
children,
...props
}: BadgeProps) {
const Comp = asChild ? Slot.Root : "span";
return (
<Comp
data-slot="badge"
data-variant={variant}
data-color={color}
data-size={size}
className={cn(badgeVariants({ variant, color, size }), className)}
{...props}
>
{dot && !asChild ? (
<>
<span
data-slot="badge-dot"
className={cn(badgeDotVariants({ size }))}
>
<span />
</span>
{children}
</>
) : (
children
)}
</Comp>
);
}
export { Badge, badgeVariants, badgeDotVariants };components/ui/badge.tsx
/* eslint-disable react-refresh/only-export-components */
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 "@/lib/utils";
const badgeVariants = cva(
"group/badge inline-flex w-fit shrink-0 items-center justify-center rounded-full border border-transparent font-medium whitespace-nowrap transition-all focus-visible:border-ring-neutral-outline focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow [&>svg]:pointer-events-none [&>svg]:shrink-0",
{
variants: {
variant: {
solid: "",
subtle: "",
outline: "",
ghost: "",
},
color: {
neutral: "",
inverse: "",
info: "",
success: "",
warning: "",
destructive: "",
accent: "",
},
size: {
sm: "h-4 gap-0.5 px-1.5 py-0.5 text-[10px] leading-[1.3] has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 [&>svg]:size-3",
md: "h-5 gap-1 px-2 py-1 text-xs leading-[1.3] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3.5",
lg: "h-6 gap-1 px-2 py-1 text-sm leading-[1.4] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-4",
},
},
compoundVariants: [
// Solid — filled surface in the colour, label inverted against it
{
variant: "solid",
color: "neutral",
className: "bg-bg-foreground text-text-inverse",
},
{
variant: "solid",
color: "inverse",
className: "bg-bg-main text-text-strong",
},
{
variant: "solid",
color: "info",
className: "bg-info-middle text-base-white",
},
{
variant: "solid",
color: "success",
className: "bg-success-middle text-base-white",
},
{
variant: "solid",
color: "warning",
className: "bg-warning-middle text-base-white",
},
{
variant: "solid",
color: "destructive",
className: "bg-negative-middle text-base-white",
},
{
variant: "solid",
color: "accent",
className: "bg-accent-default text-base-white",
},
// Subtle — tinted surface, label in the colour itself
{
variant: "subtle",
color: "neutral",
className: "bg-bg-surface-1 text-text-strong",
},
{
variant: "subtle",
color: "inverse",
className: "bg-bg-surface-inverse text-text-inverse",
},
{
variant: "subtle",
color: "info",
className: "bg-info text-info-strong",
},
{
variant: "subtle",
color: "success",
className: "bg-success text-success-middle",
},
{
variant: "subtle",
color: "warning",
className: "bg-warning text-warning-middle",
},
{
variant: "subtle",
color: "destructive",
className: "bg-negative text-negative-middle",
},
{
variant: "subtle",
color: "accent",
className: "bg-teal-50 text-accent-default",
},
// Outline — no surface, 1px border and label in the colour
{
variant: "outline",
color: "neutral",
className: "border-border-default text-text-strong",
},
{
variant: "outline",
color: "inverse",
className: "border-border-inverse text-text-inverse",
},
{
variant: "outline",
color: "info",
className: "border-info-middle text-info-middle",
},
{
variant: "outline",
color: "success",
className: "border-success-middle text-success-middle",
},
{
variant: "outline",
color: "warning",
className: "border-warning-middle text-warning-middle",
},
{
variant: "outline",
color: "destructive",
className: "border-negative-middle text-negative-middle",
},
{
variant: "outline",
color: "accent",
className: "border-accent-default text-accent-default",
},
// Ghost — transparent surface with the neutral label colour; hover fills
// in with the matching subtle surface.
{
variant: "ghost",
color: "neutral",
className: "text-text-strong hover:bg-bg-surface-1",
},
{
variant: "ghost",
color: "inverse",
className: "text-text-inverse hover:bg-bg-surface-inverse",
},
{
variant: "ghost",
color: "info",
className: "text-text-strong hover:bg-info",
},
{
variant: "ghost",
color: "success",
className: "text-text-strong hover:bg-success",
},
{
variant: "ghost",
color: "warning",
className: "text-text-strong hover:bg-warning",
},
{
variant: "ghost",
color: "destructive",
className: "text-text-strong hover:bg-negative",
},
{
variant: "ghost",
color: "accent",
className: "text-accent-default hover:bg-teal-50",
},
{ variant: "ghost", className: "bg-transparent" },
],
defaultVariants: {
variant: "solid",
color: "neutral",
size: "md",
},
},
);
// The dot sits in a slightly larger box so it keeps a small optical gap from
// its neighbours. It takes the badge's own label colour.
const badgeDotVariants = cva(
"flex shrink-0 items-center justify-center *:rounded-full *:bg-current",
{
variants: {
size: {
sm: "size-[9px] *:size-1.5",
md: "size-[11px] *:size-[7px]",
lg: "size-[14px] *:size-[9px]",
},
},
defaultVariants: {
size: "md",
},
},
);
export type BadgeProps = useRender.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { dot?: boolean };
function Badge({
className,
variant = "solid",
color = "neutral",
size = "md",
dot = false,
render,
children,
...props
}: BadgeProps) {
return useRender({
defaultTagName: "span",
props: mergeProps<"span">(
{
className: cn(badgeVariants({ variant, color, size }), className),
children:
dot && !render ? (
<>
<span
data-slot="badge-dot"
className={cn(badgeDotVariants({ size }))}
>
<span />
</span>
{children}
</>
) : (
children
),
},
props,
),
render,
state: {
slot: "badge",
variant,
color,
size,
},
});
}
export { Badge, badgeVariants, badgeDotVariants };