Skip to content

Badge

A small pill used to label status, counts or categories.

A small pill for a count, a state or a label. The dot variant puts a filled circle before the text, which is how a status reads at a glance without relying on the pill’s colour alone.

Root class newt-badge
import { Badge } from "@/registry/bases/newt/ui/badge"

export default function BadgeDemo() {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <Badge variant="brand" dot>
        Brand
      </Badge>
      <Badge variant="success" dot>
        Online
      </Badge>
      <Badge variant="danger" dot>
        Error
      </Badge>
    </div>
  )
}
<script setup lang="ts">
import { Badge } from "~~/registry/bases/newt/ui/badge"
</script>

<template>
  <div class="flex flex-wrap items-center gap-2">
    <Badge variant="brand" dot>Brand</Badge>
    <Badge variant="success" dot>Online</Badge>
    <Badge variant="danger" dot>Error</Badge>
  </div>
</template>

Installation

pnpm dlx newtui@latest add badge
npx newtui@latest add badge
yarn dlx newtui@latest add badge
bunx newtui@latest add badge

Install the dependencies

pnpm dlx --yes class-variance-authority
npx --yes class-variance-authority
yarn dlx --yes class-variance-authority
bunx --yes class-variance-authority

Copy the source into your project

import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
  "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-semibold leading-4",
  {
    variants: {
      variant: {
        default: "bg-newt-bg-elevated text-newt-text-secondary",
        brand: "bg-[rgba(88,101,242,0.15)] text-newt-mention-text",
        success: "bg-[rgba(35,165,90,0.15)] text-[#45c178]",
        warning: "bg-[rgba(240,178,50,0.15)] text-newt-mention-role",
        danger: "bg-[rgba(242,63,66,0.15)] text-[#f4878a]",
      },
      dot: {
        true: "before:h-1.5 before:w-1.5 before:rounded-full before:bg-current before:content-['']",
        false: "",
      },
    },
    defaultVariants: {
      variant: "default",
      dot: false,
    },
  }
)

export interface BadgeProps
  extends
    React.HTMLAttributes<HTMLSpanElement>,
    VariantProps<typeof badgeVariants> {}

const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
  ({ className, variant, dot, ...props }, ref) => (
    <span
      ref={ref}
      className={cn(badgeVariants({ variant, dot }), className)}
      {...props}
    />
  )
)
Badge.displayName = "Badge"

export { Badge, badgeVariants }
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

import { badgeVariants, type BadgeVariants } from "."

const props = defineProps<{
  class?: HTMLAttributes["class"]
  variant?: BadgeVariants["variant"]
  dot?: boolean
}>()
</script>

<template>
  <span
    :class="
      cn(badgeVariants({ variant: props.variant, dot: props.dot }), props.class)
    "
  >
    <slot />
  </span>
</template>
import { cva, type VariantProps } from "class-variance-authority"

export { default as Badge } from "./Badge.vue"

export const badgeVariants = cva(
  "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-semibold leading-4",
  {
    variants: {
      variant: {
        default: "bg-newt-bg-elevated text-newt-text-secondary",
        brand: "bg-[rgba(88,101,242,0.15)] text-newt-mention-text",
        success: "bg-[rgba(35,165,90,0.15)] text-[#45c178]",
        warning: "bg-[rgba(240,178,50,0.15)] text-newt-mention-role",
        danger: "bg-[rgba(242,63,66,0.15)] text-[#f4878a]",
      },
      dot: {
        true: "before:h-1.5 before:w-1.5 before:rounded-full before:bg-current before:content-['']",
        false: "",
      },
    },
    defaultVariants: {
      variant: "default",
      dot: false,
    },
  }
)

export type BadgeVariants = VariantProps<typeof badgeVariants>

Update the import paths

The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.

Usage

import { Badge } from "@/components/ui/badge"

export function Example() {
  return <Badge />
}
<script setup lang="ts">
import { Badge } from "@/components/ui/badge"
</script>

<template>
  <Badge />
</template>

API reference

Badge

PropType
variant?"default" | "brand" | "success" | "warning" | "danger"

What the badge is reporting.

Type"default" | "brand" | "success" | "warning" | "danger"
Default"default"
dot?boolean

Renders a leading dot in the current text colour.

Typeboolean
Defaultfalse

Every part also accepts the native attributes of the element it renders, and a className that is merged rather than replaced.

Accessibility

A badge is text, not a control. If it reports something that changes — an unread count, a build status — put it in a container with aria-live="polite" so the change is announced. Never let the colour be the only difference between two states: the words in the badge should differ too.

Tokens

Badge reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer. In the plain HTML and CSS distribution the same component is newt-badge.