HextaUI

Badge

色付きのドットを持つステータスラベル、スライドして閉じる削除可能なタグ、新しい値へ回転するカウントです。

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

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

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

Variant と appearance

デフォルトの outline は中立的な面を保ち、ステータスの色をドットやアイコンに付けます。バッジを目立たせたいときは appearance="solid"、控えめな塗りのチップにしたいときは appearance="muted" を使います。shape="pill" で完全な丸型になります。

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>
  )
}

サイズ

sm、default、lg。アイコン、ドット、閉じるボタン、件数はバッジに合わせて拡大縮小します。

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>
  )
}

削除可能

<BadgeClose /> を追加すると、バッジを削除可能にできます。バッジは縮んで閉じ、隣のバッジがその隙間に滑り込み、フォーカスは次の閉じるボタンに移ります。終了アニメーションを先に完了させるため、項目をデータから削除する処理は onOpenChangeComplete で行ってください。

"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>
  )
}

制御

表示状態を自分で管理するには、open と onOpenChange を渡します。open を true に戻すと、バッジが入場アニメーション付きで復活します。

"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>
  )
}

件数

<BadgeCount /> は変化した桁だけを回転させ、max(デフォルトは 99)で頭打ちにしてプラス記号を付けます。スクリーンリーダーは常に実際の数値を読み上げます。

"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>
  )
}

インタラクティブ

render を使うと、バッジをリンクやボタンにできます。ホバー時の色付け、押下時の縮小、フォーカスリングが加わります。aria-invalid でエラー状態を表示します。

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>
  )
}

長いコンテンツ

バッジがコンテナより広くなることはありません。長いラベルは三点リーダーで省略され、アイコンと閉じるボタンは表示されたままです。

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>
  )
}

右から左

アイコン、閉じるボタン、折りたたみのアニメーションは、読む方向に従います。件数は常に左から右に読みます。

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>
  )
}

これらのキーは <BadgeClose /> に適用されます。

キーアクション
EnterSpaceバッジを削除します。
BackspaceDeleteバッジを削除します。
Tab次の閉じるボタンに移動します。削除後は、フォーカスは次の閉じるボタンに移り、最後だった場合は前のボタンに移ります。
  • 閉じるボタンにはバッジの名前が付くため、スクリーンリーダーは単なる「Remove」ではなく「Remove design」と読み上げます。上書きするには aria-label を渡します。
  • <BadgeDot /> は装飾であり、支援技術からは隠されます。ステータスはテキストのラベルに含めてください。
  • <BadgeCount /> は、表示テキストが 99+ のように上限で丸められていても、正確な値を公開します。
  • モーションの低減が有効な場合、削除と件数のアニメーションはスキップされます。

<Badge /> は <span> をレンダリングし、その属性をすべて受け付けます。

プロパティ型デフォルト
variantoutline ではドットとアイコンに、solid では面に色を付けます。
"default" | "success" | "info" | "warning" | "destructive""default"
appearance
"outline" | "solid" | "muted""outline"
shape
"default" | "pill""default"
size
"sm" | "default" | "lg""default"
open表示状態を制御します。
boolean–
defaultOpen
booleantrue
onOpenChangeBadgeClose が実行されたときに呼ばれます。
(open: boolean) => void–
onOpenChangeComplete終了アニメーションの完了後に呼ばれます。項目をデータから削除する処理はここで行います。
(open: boolean) => void–
render
ReactElement | (props, state) => ReactElement<span>
属性説明
data-slot="badge"CSS でバッジを指定します。
data-variant現在のバリアント。
data-appearance現在の外観。
data-shape現在の形。
data-size現在のサイズ。
data-ending-styleバッジが退場アニメーションをしている間、付与されます。
--badge-accentドットとアイコンに使われるステータスカラー。独自のアクセントにするには上書きします。
--badge-heightバッジの高さ。
--badge-radius角丸の半径。閉じるボタンの角丸は、これをもとに算出されます。
プロパティ型デフォルト
pulseライブ状態向けにピングのアニメーションを追加します。モーションの低減が有効な場合は無効になります。
booleanfalse
属性説明
data-slot="badge-dot"CSS でドットを指定します。
data-pulsepulse が有効なときに付与されます。
プロパティ型デフォルト
children
ReactNode<IconX />
aria-label自動で付く「Remove {label}」という名前を上書きします。
string–
onClickevent.preventDefault() を呼ぶと、バッジを開いたままにします。
(event) => void–
属性説明
data-slot="badge-close"CSS で閉じるボタンを指定します。
プロパティ型デフォルト
value負の値と有限でない値は 0 と表示されます。
number–
maxこれを超える値は max+ と表示されます。上限をなくすには Infinity を渡します。
number99
duration
number600
animated
booleantrue
trend数字が回転する向き。
"auto" | "up" | "down" | "shortest""auto"
locales
Intl.LocalesArgument–
format
Intl.NumberFormatOptions–
属性説明
data-slot="badge-count"CSS で件数を指定します。

使用しているブロック

Badge の上に構築されるブロック。