HextaUI

Badge

Des libellés de statut avec pastilles colorées, des tags amovibles qui se referment en glissant et des compteurs qui défilent vers leur nouvelle valeur.

PendingIn progressIn reviewFailedSuccessExpired
import {
  IconAlertTriangle,
  IconCircleCheck,
  IconCircleX,
  IconClock,
  IconEye,
  IconLoader2,
} from "@tabler/icons-react"

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

export function BadgeDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Badge variant="warning">
        <IconAlertTriangle data-icon="inline-start" />
        Pending
      </Badge>
      <Badge variant="info">
        <IconLoader2 data-icon="inline-start" />
        In progress
      </Badge>
      <Badge variant="warning">
        <IconEye data-icon="inline-start" />
        In review
      </Badge>
      <Badge variant="destructive">
        <IconCircleX data-icon="inline-start" />
        Failed
      </Badge>
      <Badge variant="success">
        <IconCircleCheck data-icon="inline-start" />
        Success
      </Badge>
      <Badge>
        <IconClock data-icon="inline-start" />
        Expired
      </Badge>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/badge.json

Ajoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.

import {
  Badge,
  BadgeClose,
  BadgeCount,
  BadgeDot,
} from "@/components/ui/badge"
<Badge variant="success">
  <BadgeDot />
  Paid
</Badge>
Badge
├── BadgeDot
├── BadgeCount
└── BadgeClose

Variantes et apparences

L’apparence outline par défaut garde une surface neutre et place la couleur de statut sur le point ou l’icône. Utilisez appearance="solid" lorsque le badge doit ressortir, ou appearance="muted" pour une pastille pleine et discrète. shape="pill" l’arrondit complètement.

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

export function BadgeVariants() {
  return (
    <div className="flex flex-col items-center gap-3">
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Badge>
          <BadgeDot />
          Draft
        </Badge>
        <Badge variant="success">
          <BadgeDot />
          Paid
        </Badge>
        <Badge variant="info">
          <BadgeDot />
          Syncing
        </Badge>
        <Badge variant="warning">
          <BadgeDot />
          Pending
        </Badge>
        <Badge variant="destructive">
          <BadgeDot />
          Failed
        </Badge>
      </div>
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Badge appearance="solid">Draft</Badge>
        <Badge appearance="solid" variant="success">
          Paid
        </Badge>
        <Badge appearance="solid" variant="info">
          Syncing
        </Badge>
        <Badge appearance="solid" variant="warning">
          Pending
        </Badge>
        <Badge appearance="solid" variant="destructive">
          Failed
        </Badge>
      </div>
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Badge appearance="muted">Draft</Badge>
        <Badge appearance="muted" shape="pill">
          nngroup.com
        </Badge>
        <Badge appearance="muted" shape="pill" variant="info">
          <BadgeDot />
          Syncing
        </Badge>
      </div>
    </div>
  )
}

Tailles

sm, default et lg. Les icônes, points, bouton de fermeture et compteurs suivent la taille du badge.

import { IconCheck } from "@tabler/icons-react"

import { Badge, BadgeClose, BadgeCount, BadgeDot } from "@/components/ui/badge"

export function BadgeSizes() {
  return (
    <div className="flex flex-col items-center gap-3">
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Badge size="sm">Small</Badge>
        <Badge size="sm" variant="success">
          <IconCheck data-icon="inline-start" />
          Paid
        </Badge>
        <Badge size="sm" variant="info">
          <BadgeDot pulse />
          Live
        </Badge>
        <Badge size="sm">
          design
          <BadgeClose />
        </Badge>
        <Badge size="sm">
          <BadgeCount value={42} />
        </Badge>
      </div>
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Badge>Default</Badge>
        <Badge variant="success">
          <IconCheck data-icon="inline-start" />
          Paid
        </Badge>
        <Badge variant="info">
          <BadgeDot pulse />
          Live
        </Badge>
        <Badge>
          design
          <BadgeClose />
        </Badge>
        <Badge>
          <BadgeCount value={42} />
        </Badge>
      </div>
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Badge size="lg">Large</Badge>
        <Badge size="lg" variant="success">
          <IconCheck data-icon="inline-start" />
          Paid
        </Badge>
        <Badge size="lg" variant="info">
          <BadgeDot pulse />
          Live
        </Badge>
        <Badge size="lg">
          design
          <BadgeClose />
        </Badge>
        <Badge size="lg">
          <BadgeCount value={42} />
        </Badge>
      </div>
    </div>
  )
}

Amovible

Ajoutez un <BadgeClose /> pour rendre un badge amovible. Il se referme en rétrécissant et ses voisins glissent pour combler l’espace, puis le focus passe au bouton de fermeture suivant. Retirez l’élément de vos données dans onOpenChangeComplete pour que l’animation de sortie puisse d’abord se terminer.

"use client"

import * as React from "react"

import { Badge, BadgeClose } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"

const initialTags = [
  "design",
  "engineering",
  "research",
  "marketing",
  "customer-success",
  "ops",
]

export function BadgeRemovable() {
  const [tags, setTags] = React.useState(initialTags)
  const [next, setNext] = React.useState(1)

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <div className="flex flex-wrap items-center gap-2">
        {tags.map((tag) => (
          <Badge
            key={tag}
            onOpenChangeComplete={(open) => {
              if (!open) {
                setTags((current) => current.filter((item) => item !== tag))
              }
            }}
          >
            {tag}
            <BadgeClose />
          </Badge>
        ))}
      </div>
      <div className="flex flex-wrap items-center gap-2">
        <Button
          variant="outline"
          size="sm"
          onClick={() => {
            setTags((current) => [...current, `tag-${next}`])
            setNext((value) => value + 1)
          }}
        >
          Add tag
        </Button>
        <Button variant="ghost" size="sm" onClick={() => setTags(initialTags)}>
          Reset
        </Button>
      </div>
    </div>
  )
}

Contrôlé

Passez open et onOpenChange pour gérer la visibilité. Remettre open à true ramène le badge avec une animation d’entrée.

"use client"

import * as React from "react"

import { Badge, BadgeClose } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"

export function BadgeControlled() {
  const [open, setOpen] = React.useState(true)

  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Badge variant="warning" open={open} onOpenChange={setOpen}>
        Beta features on
        <BadgeClose />
      </Badge>
      <Button
        variant="outline"
        size="sm"
        disabled={open}
        onClick={() => setOpen(true)}
      >
        Restore
      </Button>
    </div>
  )
}

Compteurs

<BadgeCount /> ne fait défiler que les chiffres qui changent, et plafonne à max (99 par défaut) avec un signe plus. Les lecteurs d’écran entendent toujours le nombre réel.

"use client"

import * as React from "react"
import { IconInbox } from "@tabler/icons-react"

import { Badge, BadgeCount } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"

export function BadgeCounts() {
  const [count, setCount] = React.useState(9)

  return (
    <div className="flex flex-col items-center gap-4">
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Badge appearance="solid" variant="destructive">
          <BadgeCount value={count} />
        </Badge>
        <Badge>
          <BadgeCount value={count} max={999} />
          unread
        </Badge>
        <Button variant="outline">
          <IconInbox />
          Inbox
          <Badge size="sm">
            <BadgeCount value={count} />
          </Badge>
        </Button>
      </div>
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Button
          variant="outline"
          size="sm"
          onClick={() => setCount((value) => value + 1)}
        >
          +1
        </Button>
        <Button
          variant="outline"
          size="sm"
          onClick={() => setCount((value) => Math.max(0, value - 1))}
        >
          −1
        </Button>
        <Button
          variant="outline"
          size="sm"
          onClick={() => setCount((value) => value + 10)}
        >
          +10
        </Button>
        <Button
          variant="ghost"
          size="sm"
          onClick={() => setCount(Math.floor(Math.random() * 1200))}
        >
          Random
        </Button>
      </div>
    </div>
  )
}

Interactif

Utilisez render pour faire d’un badge un lien ou un bouton. Il gagne une teinte au survol, une réduction à la pression et un anneau de focus. aria-invalid affiche l’état d’erreur.

import { IconArrowUpRight, IconX } from "@tabler/icons-react"

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

export function BadgeInteractive() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Badge render={<a href="#changelog" />}>
        v2.0 is out
        <IconArrowUpRight data-icon="inline-end" />
      </Badge>
      <Badge render={<button type="button" />} variant="info">
        <BadgeDot />
        Filter: open
      </Badge>
      <Badge render={<a href="#new" />}>New</Badge>
      <Badge render={<button type="button" />} aria-invalid>
        <IconX data-icon="inline-start" />
        Invalid
      </Badge>
    </div>
  )
}

Contenu long

Un badge n’est jamais plus large que son conteneur. Les libellés longs sont tronqués avec des points de suspension, tandis que les icônes et le bouton de fermeture restent visibles.

import { IconCheck } from "@tabler/icons-react"

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

export function BadgeLongContent() {
  return (
    <div className="flex w-56 max-w-full flex-col items-start gap-2">
      <Badge>
        SupercalifragilisticexpialidociousSupercalifragilisticexpialidocious
      </Badge>
      <Badge variant="success">
        <IconCheck data-icon="inline-start" />
        [email protected]
        <BadgeClose />
      </Badge>
    </div>
  )
}

De droite à gauche

Les icônes, le bouton de fermeture et l’animation de repli suivent le sens de lecture. Les compteurs se lisent toujours de gauche à droite.

import { IconCheck } from "@tabler/icons-react"

import { Badge, BadgeClose, BadgeCount, BadgeDot } from "@/components/ui/badge"

export function BadgeRtl() {
  return (
    <div dir="rtl" className="flex flex-wrap items-center justify-center gap-2">
      <Badge variant="success">
        <IconCheck data-icon="inline-start" />
        مدفوع
      </Badge>
      <Badge>
        <BadgeDot pulse />
        مباشر
      </Badge>
      <Badge>
        تصميم
        <BadgeClose />
      </Badge>
      <Badge>
        <BadgeCount value={120} />
      </Badge>
    </div>
  )
}

Ces touches s’appliquent à <BadgeClose />.

ToucheAction
EnterSpaceSupprime le badge.
BackspaceDeleteSupprime le badge.
TabPasse au bouton de fermeture suivant. Après une suppression, le focus arrive sur le bouton de fermeture suivant, ou sur le précédent s’il s’agissait du dernier.
  • Le bouton de fermeture porte le nom du badge : un lecteur d’écran annonce donc « Remove design » plutôt qu’un simple « Remove ». Passez aria-label pour le remplacer.
  • <BadgeDot /> est décoratif et masqué aux technologies d’assistance. Conservez le statut dans l’étiquette textuelle.
  • <BadgeCount /> expose la valeur exacte même lorsque le texte visible est plafonné, comme 99+.
  • Les animations de suppression et de compteur sont ignorées avec la réduction des animations.

<Badge /> rend un <span> et accepte tous ses attributs.

PropTypePar défaut
variantColore le point et les icônes en outline, ou la surface en solid.
"default" | "success" | "info" | "warning" | "destructive""default"
appearance
"outline" | "solid" | "muted""outline"
shape
"default" | "pill""default"
size
"sm" | "default" | "lg""default"
openVisibilité contrôlée.
boolean–
defaultOpen
booleantrue
onOpenChangeAppelé lorsque BadgeClose est activé.
(open: boolean) => void–
onOpenChangeCompleteAppelé à la fin de l’animation de sortie. Retirez l’élément de vos données ici.
(open: boolean) => void–
render
ReactElement | (props, state) => ReactElement<span>
AttributDescription
data-slot="badge"Ciblez les badges en CSS.
data-variantLa variante actuelle.
data-appearanceL’apparence actuelle.
data-shapeLa forme actuelle.
data-sizeLa taille actuelle.
data-ending-stylePrésent pendant l’animation de sortie du badge.
--badge-accentLa couleur de statut utilisée par les points et les icônes. Remplacez-la pour un accent personnalisé.
--badge-heightLa hauteur du badge.
--badge-radiusLe rayon des coins. Le bouton de fermeture en dérive son propre rayon.
PropTypePar défaut
pulseAjoute une animation de ping pour les états en direct. Désactivée avec la réduction des animations.
booleanfalse
AttributDescription
data-slot="badge-dot"Ciblez les points en CSS.
data-pulsePrésent lorsque pulse est activé.
PropTypePar défaut
children
ReactNode<IconX />
aria-labelRemplace le nom automatique « Remove {label} ».
string–
onClickAppelez event.preventDefault() pour garder le badge ouvert.
(event) => void–
AttributDescription
data-slot="badge-close"Ciblez le bouton de fermeture en CSS.
PropTypePar défaut
valueLes valeurs négatives et non finies affichent 0.
number–
maxLes valeurs supérieures s’affichent sous la forme max+. Passez Infinity pour ne pas plafonner.
number99
duration
number600
animated
booleantrue
trendLe sens dans lequel les chiffres tournent.
"auto" | "up" | "down" | "shortest""auto"
locales
Intl.LocalesArgument–
format
Intl.NumberFormatOptions–
AttributDescription
data-slot="badge-count"Ciblez les compteurs en CSS.

Utilisé dans les blocks

Des blocks qui s’appuient sur Badge.