HextaUI

Item

リスト、設定、ピッカー向けに、メディア、テキスト、操作を並べた行です。グループ化されたサーフェスと、行の間を滑るように移動するホバーハイライトを備えています。

import {
  IconBell,
  IconDeviceDesktop,
  IconKey,
  IconUserCircle,
} from "@tabler/icons-react"

import {
  Item,
  ItemChevron,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
  type ItemMediaTone,
} from "@/components/ui/item"

const settings: {
  href: string
  icon: typeof IconBell
  title: string
  tone: ItemMediaTone
  description: string
}[] = [
  {
    href: "#profile",
    icon: IconUserCircle,
    title: "Profile",
    tone: "gray",
    description: "Name, photo and handle",
  },
  {
    href: "#notifications",
    icon: IconBell,
    title: "Notifications",
    tone: "red",
    description: "Mentions, replies and digests",
  },
  {
    href: "#security",
    icon: IconKey,
    title: "Password and passkeys",
    tone: "green",
    description: "Two passkeys, last used today",
  },
  {
    href: "#sessions",
    icon: IconDeviceDesktop,
    title: "Sessions",
    tone: "blue",
    description: "Signed in on 3 devices",
  },
]

export function ItemDemo() {
  return (
    <ItemGroup variant="grouped" className="max-w-sm">
      {settings.map((setting) => (
        <Item key={setting.href} size="sm" render={<a href={setting.href} />}>
          <ItemMedia variant="icon" tone={setting.tone}>
            <setting.icon />
          </ItemMedia>
          <ItemContent>
            <ItemTitle>{setting.title}</ItemTitle>
            <ItemDescription>{setting.description}</ItemDescription>
          </ItemContent>
          <ItemChevron />
        </Item>
      ))}
    </ItemGroup>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/item.json

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

import {
  Item,
  ItemActions,
  ItemChevron,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"
<ItemGroup variant="grouped">
  <Item render={<a href="/settings/profile" />}>
    <ItemMedia variant="icon">
      <IconUserCircle />
    </ItemMedia>
    <ItemContent>
      <ItemTitle>Profile</ItemTitle>
      <ItemDescription>Name, photo and handle</ItemDescription>
    </ItemContent>
    <ItemChevron />
  </Item>
</ItemGroup>

iOS の設定のような色付きのタイルにするには、アイコンのメディアに tone を指定し、中立的なタイルにするには未設定のままにします。項目は、render でリンク、ボタン、ラベルとしてレンダリングされると、インタラクティブになります。そのときに初めてホバー、押下、フォーカスのスタイルが付くため、静的な行がクリックできるように見えることはありません。

ItemGroup
├── Item
│   ├── ItemHeader
│   ├── ItemMedia
│   ├── ItemContent
│   │   ├── ItemTitle
│   │   └── ItemDescription
│   ├── ItemActions
│   ├── ItemChevron
│   └── ItemFooter
└── ItemSeparator

グループ化

variant="grouped" は項目を1つの面にまとめます。区切り線は自動で描画されてテキストの位置まで内側に寄り、先頭行と末尾行が外側の角を担い、ホバーまたはフォーカスされた行の隣の区切り線はフェードアウトします。

"use client"

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

import {
  Item,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemTitle,
} from "@/components/ui/item"

const themes = [
  { id: "system", title: "System", description: "Follow your device" },
  { id: "light", title: "Light", description: "Always light" },
  { id: "dark", title: "Dark", description: "Always dark" },
]

export function ItemPressed() {
  const [theme, setTheme] = React.useState("system")

  return (
    <ItemGroup variant="grouped" className="max-w-sm">
      {themes.map((option) => (
        <Item
          key={option.id}
          size="sm"
          render={<button type="button" />}
          aria-pressed={theme === option.id}
          onClick={() => setTheme(option.id)}
        >
          <ItemContent>
            <ItemTitle>{option.title}</ItemTitle>
            <ItemDescription>{option.description}</ItemDescription>
          </ItemContent>
          {theme === option.id && (
            <IconCheck className="size-4 text-foreground" aria-hidden="true" />
          )}
        </Item>
      ))}
    </ItemGroup>
  )
}

ホバー時のハイライト

グループ内のインタラクティブな項目は、1 つのハイライトを共有します。ハイライトはポインターの下の行に滑らかに移動し、その行の角を取り込みます。マウスまたはペンにのみ追従し、タッチには追従しません。オフにするには highlight={false} を指定します。画像メディアの角は、行と同心です。

import {
  Item,
  ItemChevron,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

const albums = [
  {
    title: "Night Drive",
    artist: "Lune Avenue",
    src: "https://images.unsplash.com/photo-1514525253161-7a46d19cd819?w=120&h=120&fit=crop",
  },
  {
    title: "Paper Gardens",
    artist: "Mara Lin",
    src: "https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=120&h=120&fit=crop",
  },
  {
    title: "Low Tide",
    artist: "The Harbour",
    src: "https://images.unsplash.com/photo-1511379938547-c1f69419868d?w=120&h=120&fit=crop",
  },
]

export function ItemImage() {
  return (
    <ItemGroup className="max-w-sm">
      {albums.map((album) => (
        <Item key={album.title} size="sm" render={<a href="#" />}>
          <ItemMedia variant="image">
            <img src={album.src} alt="" width={40} height={40} />
          </ItemMedia>
          <ItemContent>
            <ItemTitle>{album.title}</ItemTitle>
            <ItemDescription>{album.artist}</ItemDescription>
          </ItemContent>
          <ItemChevron />
        </Item>
      ))}
    </ItemGroup>
  )
}

選択可能

項目を Checkbox を囲む <label> として描画すると、行全体でチェックを切り替えられます。チェック済みのコントロール、または項目の aria-selected、aria-checked、aria-pressed によって、その行が選択状態になります。

import { Checkbox } from "@/components/ui/checkbox"
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemTitle,
} from "@/components/ui/item"

const plans = [
  {
    id: "analytics",
    title: "Analytics",
    description: "Page views and funnels",
  },
  { id: "backups", title: "Daily backups", description: "Kept for 30 days" },
  { id: "sso", title: "Single sign-on", description: "SAML and OIDC" },
]

export function ItemSelectable() {
  return (
    <ItemGroup className="max-w-sm">
      {plans.map((plan, index) => (
        <Item key={plan.id} variant="outline" size="sm" render={<label />}>
          <Checkbox
            name="addons"
            value={plan.id}
            defaultChecked={index === 0}
          />
          <ItemContent>
            <ItemTitle>{plan.title}</ItemTitle>
            <ItemDescription>{plan.description}</ItemDescription>
          </ItemContent>
        </Item>
      ))}
    </ItemGroup>
  )
}

バリアント

default は背景なし、outline は細い境界線、muted は淡い塗りつぶしです。

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

import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

const variants = ["default", "outline", "muted"] as const

export function ItemVariants() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      {variants.map((variant) => (
        <Item key={variant} variant={variant}>
          <ItemMedia variant="icon">
            <IconFileText />
          </ItemMedia>
          <ItemContent>
            <ItemTitle>Quarterly report.pdf</ItemTitle>
            <ItemDescription>2.4 MB · {variant}</ItemDescription>
          </ItemContent>
          <ItemActions>
            <Button variant="outline" size="sm">
              Open
            </Button>
          </ItemActions>
        </Item>
      ))}
    </div>
  )
}

サイズ

size はパディング、gap、角丸、メディアをまとめて拡大縮小します。グループも gap を合わせて詰まります。

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

import {
  Item,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

const sizes = ["default", "sm", "xs"] as const

export function ItemSizes() {
  return (
    <ItemGroup className="max-w-sm">
      {sizes.map((size) => (
        <Item key={size} size={size} variant="outline">
          <ItemMedia variant="icon">
            <IconMusic />
          </ItemMedia>
          <ItemContent>
            <ItemTitle>Size {size}</ItemTitle>
            <ItemDescription>
              Padding, gap and media scale together.
            </ItemDescription>
          </ItemContent>
        </Item>
      ))}
    </ItemGroup>
  )
}

Avatar

デフォルトの <ItemMedia /> は内容を中央に配置するだけなので、Avatar や AvatarGroup をそのまま置けます。

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

import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
} from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

const team = [
  { handle: "preetsuthar17", initials: "PS" },
  { handle: "emilkowalski", initials: "EK" },
  { handle: "rauchg", initials: "GR" },
]

export function ItemAvatar() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-6">
      <Item variant="outline">
        <ItemMedia>
          <Avatar size="lg">
            <AvatarImage src="https://github.com/preetsuthar17.png" alt="" />
            <AvatarFallback>PS</AvatarFallback>
          </Avatar>
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Preet Suthar</ItemTitle>
          <ItemDescription>Maintainer of HextaUI</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button
            variant="outline"
            size="icon-sm"

            aria-label="Invite Preet Suthar"
          >
            <IconPlus />
          </Button>
        </ItemActions>
      </Item>
      <Item variant="outline">
        <ItemMedia>
          <AvatarGroup>
            {team.map((person) => (
              <Avatar key={person.handle}>
                <AvatarImage
                  src={`https://github.com/${person.handle}.png`}
                  alt=""
                />
                <AvatarFallback>{person.initials}</AvatarFallback>
              </Avatar>
            ))}
          </AvatarGroup>
        </ItemMedia>
        <ItemContent>
          <ItemTitle>No team members</ItemTitle>
          <ItemDescription>Invite your team to collaborate.</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button size="sm" variant="outline">
            Invite
          </Button>
        </ItemActions>
      </Item>
    </div>
  )
}

ユーザー

人物のグループ化されたリスト。セパレーターは、メディアスロットの内容にかかわらずテキストの開始位置から始まるため、名前の下に揃います。

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

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

const people = [
  { handle: "preetsuthar17", name: "Preet Suthar" },
  { handle: "emilkowalski", name: "Emil Kowalski" },
  { handle: "rauchg", name: "Guillermo Rauch" },
  { handle: "leerob", name: "Lee Robinson" },
]

export function ItemPeople() {
  return (
    <ItemGroup variant="grouped" className="max-w-sm">
      {people.map((person) => (
        <Item key={person.handle} size="sm">
          <ItemMedia>
            <Avatar size="lg">
              <AvatarImage
                src={`https://github.com/${person.handle}.png`}
                alt=""
              />
              <AvatarFallback>
                {person.name
                  .split(" ")
                  .map((part) => part[0])
                  .join("")}
              </AvatarFallback>
            </Avatar>
          </ItemMedia>
          <ItemContent>
            <ItemTitle>{person.name}</ItemTitle>
            <ItemDescription>@{person.handle}</ItemDescription>
          </ItemContent>
          <ItemActions>
            <Button
              variant="ghost"
              size="icon-sm"

              aria-label={`Invite ${person.name}`}
            >
              <IconPlus />
            </Button>
          </ItemActions>
        </Item>
      ))}
    </ItemGroup>
  )
}

アクション

<ItemActions /> は、行の末尾にボタンを保持します。ボタンを含む項目自体は静的なままにして、リンクの中にボタンが入ることがないようにしてください。

import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

const invites = [
  { initials: "MO", name: "Mira Okafor", email: "[email protected]" },
  { initials: "JP", name: "Jun Park", email: "[email protected]" },
]

export function ItemActionsDemo() {
  return (
    <ItemGroup variant="grouped" className="max-w-md">
      {invites.map((invite) => (
        <Item key={invite.email} size="sm">
          <ItemMedia>
            <Avatar>
              <AvatarFallback>{invite.initials}</AvatarFallback>
            </Avatar>
          </ItemMedia>
          <ItemContent>
            <ItemTitle>{invite.name}</ItemTitle>
            <ItemDescription>{invite.email}</ItemDescription>
          </ItemContent>
          <ItemActions>
            <Button variant="ghost" size="sm">
              Decline
            </Button>
            <Button variant="outline" size="sm">
              Accept
            </Button>
          </ItemActions>
        </Item>
      ))}
    </ItemGroup>
  )
}

<ItemHeader /> と <ItemFooter /> は、コンテンツの上と下で一行分の幅を占めます。

import { Badge } from "@/components/ui/badge"
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemFooter,
  ItemHeader,
  ItemTitle,
} from "@/components/ui/item"

export function ItemHeaderFooter() {
  return (
    <Item variant="outline" className="max-w-sm" render={<a href="#" />}>
      <ItemHeader>
        <span className="text-xs text-muted-foreground">hextaui/hextaui</span>
        <Badge>Open</Badge>
      </ItemHeader>
      <ItemContent>
        <ItemTitle>Rework the item component</ItemTitle>
        <ItemDescription>
          Grouped surfaces, a gliding hover highlight and selectable rows.
        </ItemDescription>
      </ItemContent>
      <ItemFooter>
        <span className="text-xs text-muted-foreground">
          #482 · 3 reviewers
        </span>
        <span className="text-xs text-muted-foreground">2h ago</span>
      </ItemFooter>
    </Item>
  )
}

Separator

デフォルトのグループでは、項目の間に <ItemSeparator /> を自分で配置します。

import {
  Item,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemSeparator,
  ItemTitle,
} from "@/components/ui/item"

export function ItemSeparatorDemo() {
  return (
    <ItemGroup className="max-w-sm">
      <Item size="sm">
        <ItemContent>
          <ItemTitle>Storage</ItemTitle>
          <ItemDescription>18.2 GB of 50 GB used</ItemDescription>
        </ItemContent>
      </Item>
      <ItemSeparator />
      <Item size="sm">
        <ItemContent>
          <ItemTitle>Bandwidth</ItemTitle>
          <ItemDescription>312 GB this month</ItemDescription>
        </ItemContent>
      </Item>
    </ItemGroup>
  )
}

長いコンテンツ

タイトルは1行、説明は2行で切り詰められます。区切りのない文字列は行を広げずに折り返されます。

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

import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

export function ItemLongContent() {
  return (
    <Item variant="outline" className="max-w-sm">
      <ItemMedia variant="icon">
        <IconFile />
      </ItemMedia>
      <ItemContent>
        <ItemTitle>
          final-final-v3-approved-by-legal-and-design-do-not-edit-this-copy.pdf
        </ItemTitle>
        <ItemDescription>
          Uploaded to /projects/2026/client-work/very-long-folder-names/archive
          by [email protected] and shared with
          the whole organisation.
        </ItemDescription>
      </ItemContent>
      <ItemActions>
        <Button variant="outline" size="sm">
          Share
        </Button>
      </ItemActions>
    </Item>
  )
}

右から左

パディング、区切り線のインセット、シェブロンは文字の読み方向に従います。

import { IconBell, IconUserCircle } from "@tabler/icons-react"

import {
  Item,
  ItemChevron,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

export function ItemRtl() {
  return (
    <div dir="rtl" className="w-full max-w-sm">
      <ItemGroup variant="grouped">
        <Item size="sm" render={<a href="#" />}>
          <ItemMedia variant="icon">
            <IconUserCircle />
          </ItemMedia>
          <ItemContent>
            <ItemTitle>الملف الشخصي</ItemTitle>
            <ItemDescription>الاسم والصورة</ItemDescription>
          </ItemContent>
          <ItemChevron />
        </Item>
        <Item size="sm" render={<a href="#" />}>
          <ItemMedia variant="icon">
            <IconBell />
          </ItemMedia>
          <ItemContent>
            <ItemTitle>الإشعارات</ItemTitle>
            <ItemDescription>الإشارات والردود</ItemDescription>
          </ItemContent>
          <ItemChevron />
        </Item>
      </ItemGroup>
    </div>
  )
}
キーアクション
Tabリンクやボタンと同様に、ソースの順で次のインタラクティブな項目に移動します。
Enterリンクの項目では遷移し、ボタンの項目では押下されます。
Spaceボタン項目を押すか、ラベル項目を切り替えます。
  • 項目がプレーンな行である場合、グループはリストになります。リンクやボタンとしてレンダリングされた項目は独自の role を保ち、グループは list の role を外すため、マークアップは有効なままです。両方が必要な場合は、グループを <ul> として、項目をリンクを包む <li> としてレンダリングしてください。
  • <ItemMedia variant="icon" /> と山形アイコンは、スクリーンリーダーから隠されます。画像に意味がある場合は、適切な alt テキストを付けてください。
  • ハイライトは装飾です。キーボードフォーカスにはフォーカスリングが表示され、ハイライトがフォーカスを移動したりタブ順を変えたりすることはありません。

各パーツはデフォルトで <div> をレンダリングし、render と、その要素の属性を受け付けます。

プロパティ型デフォルト
variant
"default" | "outline" | "muted""default"
size
"default" | "sm" | "xs""default"
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="item"CSSで項目を指定します。
data-variant現在のバリアント。
data-size現在のサイズ。
data-interactive項目がリンク、ボタン、ラベルとして描画されるときに付きます。
data-highlightedグループのハイライトが当たっている間付きます。
--item-radius角丸の半径。メディアの角はこれから算出されます。size によって設定されます。
--item-px / --item-py / --item-gapパディングと間隔。size によって設定されます。
--item-media-sizeアイコンと画像メディアのサイズ。size によって設定されます。
--item-insetグループ化された区切り線の開始位置。メディアが何であっても ItemContent に揃うように計測されます。
プロパティ型デフォルト
variantgrouped は項目を1つの面にまとめ、区切り線を自動で付けます。
"default" | "grouped""default"
highlightホバー時に、操作可能な項目の間を滑らかに移動するハイライトを1つ表示します。
booleantrue
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="item-group"CSSでグループを指定します。
data-variant現在のバリアント。
data-highlightホバーハイライトが有効なときに付きます。
--item-group-radiusグループ化された面の角丸の半径。最初と最後の項目はこれに従います。
data-slot="item-highlight"ハイライト要素。data-visible と data-pressed を持ちます。
プロパティ型デフォルト
varianticon はアイコンをタイルに載せ、スクリーンリーダーからは隠します。image は正方形に切り抜きます。どちらも角は項目と同心になります。
"default" | "icon" | "image""default"
toneicon バリアントでは、iOS の設定アプリのように、タイルを単色で塗りつぶしてアイコンを白にします。未設定ならニュートラルなタイルになります。
"gray" | "red" | "orange" | "yellow" | "green" | "teal" | "sky" | "blue" | "indigo" | "purple" | "pink"–
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="item-media"CSS でメディアを指定します。
data-variant現在のバリアント。
data-toneアイコンに tone を設定したときのタイルの色。
--item-media-toneタイルの色。任意の色を指定するとカスタムトーンになります。
属性説明
data-slot="item-content"行を埋めるように広がります。2 つ目のコンテンツブロックは、テキストの幅に収まります。
属性説明
data-slot="item-title"1 行に収めます。
プロパティ型デフォルト
render
ReactElement | (props, state) => ReactElement<p>
属性説明
data-slot="item-description"2 行に収めます。
属性説明
data-slot="item-actions"CSS でアクションを指定します。
属性説明
data-slot="item-header"コンテンツの上にある全幅の行。
data-slot="item-footer"コンテンツの下にある全幅の行。

末尾の山形アイコン。項目にホバーすると、読む方向にわずかに動きます。Tabler アイコンのすべての props を受け付けます。

属性説明
data-slot="item-chevron"CSS でシェブロンを指定します。
属性説明
data-slot="item-separator"role="separator" を持つ細い線。