Github

Item

A versatile component for displaying content with media, title, description, and actions.

Basic Item

A simple item with title and description.

Your profile has been verified.
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
	Item,
	ItemActions,
	ItemContent,
	ItemDescription,
	ItemMedia,
	ItemTitle,
} from "@/components/ui/item";

export function ItemDemo() {
	return (
		<div className="flex w-full max-w-md flex-col gap-6">
			<Item variant="outline">
				<ItemContent>
					<ItemTitle>Basic Item</ItemTitle>
					<ItemDescription>
						A simple item with title and description.
					</ItemDescription>
				</ItemContent>
				<ItemActions>
					<Button variant="outline" size="sm">
						Action
					</Button>
				</ItemActions>
			</Item>
			<Item
				variant="outline"
				size="sm"
				render={<a href="#" aria-label="Verified profile details" />}
			>
				<ItemMedia>
					<BadgeCheckIcon className="size-5" />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Your profile has been verified.</ItemTitle>
				</ItemContent>
				<ItemActions>
					<ChevronRightIcon className="size-4 rtl:rotate-180" />
				</ItemActions>
			</Item>
		</div>
	);
}

Installation

pnpm dlx shadcn@latest add https://herocn.dev/r/item.json

Usage

import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"
<Item>
  <ItemMedia variant="icon">
    <Icon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Title</ItemTitle>
    <ItemDescription>Description</ItemDescription>
  </ItemContent>
  <ItemActions>
    <Button>Action</Button>
  </ItemActions>
</Item>

Composition

Use the following composition to build an Item:

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

Item vs Field

Use Field if you need to display a form control such as a checkbox, input, radio, or select.

If you only need to display content such as a title, description, and actions, use Item.

Variant

Use the variant prop to change the visual style of the item.

Default

Surface background with shadow for emphasis.

Secondary

Secondary surface for layered layouts.

Tertiary

Tertiary surface for the quietest blocks.

Outline

Outlined style with a visible border.

Transparent

No background—spacing and focus styles only.

import { InboxIcon } from "lucide-react";
import {
	Item,
	ItemContent,
	ItemDescription,
	ItemMedia,
	ItemTitle,
} from "@/components/ui/item";

export function ItemVariants() {
	return (
		<div className="flex w-full max-w-md flex-col gap-6">
			<Item>
				<ItemMedia variant="icon">
					<InboxIcon />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Default</ItemTitle>
					<ItemDescription>
						Surface background with shadow for emphasis.
					</ItemDescription>
				</ItemContent>
			</Item>
			<Item variant="secondary">
				<ItemMedia variant="icon">
					<InboxIcon />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Secondary</ItemTitle>
					<ItemDescription>
						Secondary surface for layered layouts.
					</ItemDescription>
				</ItemContent>
			</Item>
			<Item variant="tertiary">
				<ItemMedia variant="icon">
					<InboxIcon />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Tertiary</ItemTitle>
					<ItemDescription>
						Tertiary surface for the quietest blocks.
					</ItemDescription>
				</ItemContent>
			</Item>
			<Item variant="outline">
				<ItemMedia variant="icon">
					<InboxIcon />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Outline</ItemTitle>
					<ItemDescription>
						Outlined style with a visible border.
					</ItemDescription>
				</ItemContent>
			</Item>
			<Item variant="transparent">
				<ItemMedia variant="icon">
					<InboxIcon />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Transparent</ItemTitle>
					<ItemDescription>
						No background—spacing and focus styles only.
					</ItemDescription>
				</ItemContent>
			</Item>
		</div>
	);
}

Size

Use the size prop to change the padding and density of the item. Available sizes are default, sm, and xs.

Default size

The standard size for most use cases.

Small

A compact size for dense layouts.

Extra small

The most compact size available.

import { InboxIcon } from "lucide-react";
import {
	Item,
	ItemContent,
	ItemDescription,
	ItemMedia,
	ItemTitle,
} from "@/components/ui/item";

export function ItemSizes() {
	return (
		<div className="flex w-full max-w-md flex-col gap-6">
			<Item variant="outline">
				<ItemMedia variant="icon">
					<InboxIcon />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Default size</ItemTitle>
					<ItemDescription>
						The standard size for most use cases.
					</ItemDescription>
				</ItemContent>
			</Item>
			<Item variant="outline" size="sm">
				<ItemMedia variant="icon">
					<InboxIcon />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Small</ItemTitle>
					<ItemDescription>A compact size for dense layouts.</ItemDescription>
				</ItemContent>
			</Item>
			<Item variant="outline" size="xs">
				<ItemMedia variant="icon">
					<InboxIcon />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Extra small</ItemTitle>
					<ItemDescription>The most compact size available.</ItemDescription>
				</ItemContent>
			</Item>
		</div>
	);
}

Examples

Icon

Use ItemMedia with variant="icon" to display an icon.

Security alert

New login detected from unknown device.

import { ShieldAlertIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
	Item,
	ItemActions,
	ItemContent,
	ItemDescription,
	ItemMedia,
	ItemTitle,
} from "@/components/ui/item";

export function ItemIcon() {
	return (
		<div className="flex w-full max-w-lg flex-col gap-6">
			<Item variant="outline">
				<ItemMedia variant="icon">
					<ShieldAlertIcon />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Security alert</ItemTitle>
					<ItemDescription>
						New login detected from unknown device.
					</ItemDescription>
				</ItemContent>
				<ItemActions>
					<Button size="sm" variant="outline">
						Review
					</Button>
				</ItemActions>
			</Item>
		</div>
	);
}

Avatar

Use ItemMedia with the default variant to wrap an Avatar or other custom media.

ER
Evil Rabbit

Last seen 5 months ago

ER
No team members

Invite your team to collaborate on this project.

import { PlusIcon } from "lucide-react";
import {
	Avatar,
	AvatarFallback,
	AvatarImage,
} from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
	Item,
	ItemActions,
	ItemContent,
	ItemDescription,
	ItemMedia,
	ItemTitle,
} from "@/components/ui/item";

export function ItemAvatar() {
	return (
		<div className="flex w-full max-w-lg flex-col gap-6">
			<Item variant="outline">
				<ItemMedia>
					<Avatar className="size-10">
						<AvatarImage src="https://github.com/evilrabbit.png" alt="" />
						<AvatarFallback>ER</AvatarFallback>
					</Avatar>
				</ItemMedia>
				<ItemContent>
					<ItemTitle>Evil Rabbit</ItemTitle>
					<ItemDescription>Last seen 5 months ago</ItemDescription>
				</ItemContent>
				<ItemActions>
					<Button
						size="icon-sm"
						variant="outline"
						className="rounded-full"
						aria-label="Invite"
					>
						<PlusIcon />
					</Button>
				</ItemActions>
			</Item>
			<Item variant="outline">
				<ItemMedia>
					<div className="flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
						<Avatar className="hidden sm:flex">
							<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
							<AvatarFallback>CN</AvatarFallback>
						</Avatar>
						<Avatar className="hidden sm:flex">
							<AvatarImage
								src="https://github.com/maxleiter.png"
								alt="@maxleiter"
							/>
							<AvatarFallback>LR</AvatarFallback>
						</Avatar>
						<Avatar>
							<AvatarImage
								src="https://github.com/evilrabbit.png"
								alt="@evilrabbit"
							/>
							<AvatarFallback>ER</AvatarFallback>
						</Avatar>
					</div>
				</ItemMedia>
				<ItemContent>
					<ItemTitle>No team members</ItemTitle>
					<ItemDescription>
						Invite your team to collaborate on this project.
					</ItemDescription>
				</ItemContent>
				<ItemActions>
					<Button size="sm" variant="outline">
						Invite
					</Button>
				</ItemActions>
			</Item>
		</div>
	);
}

Image

Use ItemMedia with variant="image" for square, cropped thumbnails (for example album art or list thumbnails).

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

const music = [
	{
		title: "Midnight City Lights",
		artist: "Neon Dreams",
		album: "Electric Nights",
		duration: "3:45",
	},
	{
		title: "Coffee Shop Conversations",
		artist: "The Morning Brew",
		album: "Urban Stories",
		duration: "4:05",
	},
	{
		title: "Digital Rain",
		artist: "Cyber Symphony",
		album: "Binary Beats",
		duration: "3:30",
	},
];

export function ItemImage() {
	return (
		<div className="flex w-full max-w-md flex-col gap-6">
			<ItemGroup className="gap-4">
				{music.map((song) => (
					<Item
						key={song.title}
						variant="outline"
						render={<a href="#" aria-label={`Play ${song.title}`} />}
						role="listitem"
					>
						<ItemMedia variant="image">
							<img
								src={`https://avatar.vercel.sh/${encodeURIComponent(song.title)}`}
								alt=""
								width={32}
								height={32}
								className="object-cover grayscale"
							/>
						</ItemMedia>
						<ItemContent>
							<ItemTitle className="line-clamp-1">
								{song.title} —{" "}
								<span className="text-muted-foreground">{song.album}</span>
							</ItemTitle>
							<ItemDescription>{song.artist}</ItemDescription>
						</ItemContent>
						<ItemContent className="flex-none text-center">
							<ItemDescription>{song.duration}</ItemDescription>
						</ItemContent>
					</Item>
				))}
			</ItemGroup>
		</div>
	);
}

Group

Use ItemGroup to group related items. It sets role="list" and adjusts spacing when items use smaller sizes.

0
0xMaqed

Copy/Paster

s
shadcn

Creator of shadcn

m
maxleiter

SWE

import { PlusIcon } from "lucide-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 = [
	{
		username: "0xMaqed",
		avatar: "https://github.com/Maqed.png",
		role: "Copy/Paster",
	},
	{
		username: "shadcn",
		avatar: "https://github.com/shadcn.png",
		role: "Creator of shadcn",
	},
	{
		username: "maxleiter",
		avatar: "https://github.com/maxleiter.png",
		role: "SWE",
	},
];

export function ItemGroupExample() {
	return (
		<ItemGroup className="max-w-sm">
			{people.map((person) => (
				<Item key={person.username} variant="outline">
					<ItemMedia>
						<Avatar>
							<AvatarImage src={person.avatar} alt="" />
							<AvatarFallback>{person.username.charAt(0)}</AvatarFallback>
						</Avatar>
					</ItemMedia>
					<ItemContent className="gap-1">
						<ItemTitle>{person.username}</ItemTitle>
						<ItemDescription>{person.role}</ItemDescription>
					</ItemContent>
					<ItemActions>
						<Button variant="ghost" size="icon" className="rounded-full">
							<PlusIcon />
						</Button>
					</ItemActions>
				</Item>
			))}
		</ItemGroup>
	);
}

Use ItemHeader for full-width content above the main row (for example a cover image or banner).

v0-1.5-sm

Everyday tasks and UI generation.

v0-1.5-lg

Advanced thinking or reasoning.

v0-2.0-mini

Open source model for everyone.

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

const models = [
	{
		name: "v0-1.5-sm",
		description: "Everyday tasks and UI generation.",
		image:
			"https://images.unsplash.com/photo-1650804068570-7fb2e3dbf888?q=80&w=640&auto=format&fit=crop",
	},
	{
		name: "v0-1.5-lg",
		description: "Advanced thinking or reasoning.",
		image:
			"https://images.unsplash.com/photo-1610280777472-54133d004c8c?q=80&w=640&auto=format&fit=crop",
	},
	{
		name: "v0-2.0-mini",
		description: "Open source model for everyone.",
		image:
			"https://images.unsplash.com/photo-1602146057681-08560aee8cde?q=80&w=640&auto=format&fit=crop",
	},
];

export function ItemHeaderDemo() {
	return (
		<div className="flex w-full max-w-xl flex-col gap-6">
			<ItemGroup className="grid grid-cols-1 gap-4 sm:grid-cols-3">
				{models.map((model) => (
					<Item key={model.name} variant="outline">
						<ItemHeader>
							<img
								src={model.image}
								alt=""
								width={128}
								height={128}
								className="aspect-square w-full rounded-sm object-cover"
							/>
						</ItemHeader>
						<ItemContent>
							<ItemTitle>{model.name}</ItemTitle>
							<ItemDescription>{model.description}</ItemDescription>
						</ItemContent>
					</Item>
				))}
			</ItemGroup>
		</div>
	);
}

Use the render prop so the root becomes a link (or button). Focus and hover styles apply to that element.

import { ChevronRightIcon, ExternalLinkIcon } from "lucide-react";
import {
	Item,
	ItemActions,
	ItemContent,
	ItemDescription,
	ItemTitle,
} from "@/components/ui/item";

export function ItemLink() {
	return (
		<div className="flex w-full max-w-md flex-col gap-4">
			<Item render={<a href="#" aria-label="Open documentation" />}>
				<ItemContent>
					<ItemTitle>Visit our documentation</ItemTitle>
					<ItemDescription>
						Learn how to get started with our components.
					</ItemDescription>
				</ItemContent>
				<ItemActions>
					<ChevronRightIcon className="size-4 rtl:rotate-180" />
				</ItemActions>
			</Item>
			<Item
				variant="outline"
				render={
					<a
						href="#"
						target="_blank"
						rel="noopener noreferrer"
						aria-label="Open external resource in a new tab"
					/>
				}
			>
				<ItemContent>
					<ItemTitle>External resource</ItemTitle>
					<ItemDescription>
						Opens in a new tab with security attributes.
					</ItemDescription>
				</ItemContent>
				<ItemActions>
					<ExternalLinkIcon className="size-4" />
				</ItemActions>
			</Item>
		</div>
	);
}
<Item render={<a href="/dashboard" />}>
  <ItemMedia variant="icon">
    <HomeIcon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Dashboard</ItemTitle>
    <ItemDescription>Overview of your account and activity.</ItemDescription>
  </ItemContent>
</Item>

Compose a compact Item inside menu rows—for example, avatar plus text in a DropdownMenuItem.

"use client";

import { ChevronDownIcon } from "lucide-react";
import {
	Avatar,
	AvatarFallback,
	AvatarImage,
} from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
	DropdownMenu,
	DropdownMenuContent,
	DropdownMenuGroup,
	DropdownMenuItem,
	DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
	Item,
	ItemContent,
	ItemDescription,
	ItemMedia,
	ItemTitle,
} from "@/components/ui/item";

const people = [
	{
		username: "0xMaqed",
		avatar: "https://github.com/Maqed.png",
	},
	{
		username: "shadcn",
		avatar: "https://github.com/shadcn.png",
	},
	{
		username: "evilrabbit",
		avatar: "https://github.com/evilrabbit.png",
	},
];

export function ItemDropdown() {
	return (
		<DropdownMenu>
			<DropdownMenuTrigger render={<Button variant="tertiary" />}>
				Select <ChevronDownIcon />
			</DropdownMenuTrigger>
			<DropdownMenuContent className="w-48" align="end">
				<DropdownMenuGroup>
					{people.map((person) => (
						<DropdownMenuItem key={person.username}>
							<Item size="xs" className="w-full p-2">
								<ItemMedia>
									<Avatar className="size-8">
										<AvatarImage src={person.avatar} alt="" />
										<AvatarFallback>{person.username.charAt(0)}</AvatarFallback>
									</Avatar>
								</ItemMedia>
								<ItemContent className="gap-0">
									<ItemTitle>{person.username}</ItemTitle>
								</ItemContent>
							</Item>
						</DropdownMenuItem>
					))}
				</DropdownMenuGroup>
			</DropdownMenuContent>
		</DropdownMenu>
	);
}

RTL

To enable RTL support in herocn, see the RTL configuration guide. Directional icons such as chevrons should use rtl:rotate-180 where appropriate.

عنصر أساسي

عنصر بسيط يحتوي على عنوان ووصف.

تم التحقق من ملفك الشخصي.
"use client";

import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react";
import {
	type Translations,
	useTranslation,
} from "@/components/language-selector";
import { Button } from "@/components/ui/button";
import {
	Item,
	ItemActions,
	ItemContent,
	ItemDescription,
	ItemMedia,
	ItemTitle,
} from "@/components/ui/item";

const translations: Translations = {
	en: {
		dir: "ltr",
		values: {
			basicItem: "Basic Item",
			basicItemDesc: "A simple item with title and description.",
			action: "Action",
			verifiedTitle: "Your profile has been verified.",
		},
	},
	ar: {
		dir: "rtl",
		values: {
			basicItem: "عنصر أساسي",
			basicItemDesc: "عنصر بسيط يحتوي على عنوان ووصف.",
			action: "إجراء",
			verifiedTitle: "تم التحقق من ملفك الشخصي.",
		},
	},
	he: {
		dir: "rtl",
		values: {
			basicItem: "פריט בסיסי",
			basicItemDesc: "פריט פשוט עם כותרת ותיאור.",
			action: "פעולה",
			verifiedTitle: "הפרופיל שלך אומת.",
		},
	},
};

export function ItemRtl() {
	const { dir, t, language } = useTranslation(translations, "ar");

	return (
		<div
			className="flex w-full max-w-md flex-col gap-6"
			lang={language}
			dir={dir}
		>
			<Item variant="outline">
				<ItemContent>
					<ItemTitle>{t.basicItem}</ItemTitle>
					<ItemDescription>{t.basicItemDesc}</ItemDescription>
				</ItemContent>
				<ItemActions>
					<Button variant="outline" size="sm">
						{t.action}
					</Button>
				</ItemActions>
			</Item>
			<Item
				variant="outline"
				size="sm"
				render={<a href="#" aria-label={t.verifiedTitle} />}
			>
				<ItemMedia>
					<BadgeCheckIcon className="size-5" />
				</ItemMedia>
				<ItemContent>
					<ItemTitle>{t.verifiedTitle}</ItemTitle>
				</ItemContent>
				<ItemActions>
					<ChevronRightIcon className="size-4 rtl:rotate-180" />
				</ItemActions>
			</Item>
		</div>
	);
}

API Reference

Item

PropTypeDefault
variant"default" | "secondary" | "tertiary" | "outline" | "muted" | "transparent"
"default"
size"default" | "sm" | "xs"
"default"

ItemGroup

Container with role="list" and vertical spacing. Accepts standard div props.

ItemSeparator

Horizontal separator between items, built on the Separator component.

ItemMedia

PropTypeDefault