A simple item with title and description.
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>
);
}
pnpm dlx shadcn@latest add https://herocn.dev/r/item.jsonimport {
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>Use the following composition to build an Item:
ItemGroup
└── Item
├── ItemHeader
├── ItemMedia
├── ItemContent
│ ├── ItemTitle
│ └── ItemDescription
├── ItemActions
└── ItemFooterUse 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.
Use the variant prop to change the visual style of the item.
Surface background with shadow for emphasis.
Secondary surface for layered layouts.
Tertiary surface for the quietest blocks.
Outlined style with a visible border.
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>
);
}
Use the size prop to change the padding and density of the item. Available sizes are default, sm, and xs.
The standard size for most use cases.
A compact size for dense layouts.
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>
);
}
Use ItemMedia with variant="icon" to display an icon.
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>
);
}
Use ItemMedia with the default variant to wrap an Avatar or other custom media.
Last seen 5 months ago
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>
);
}
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>
);
}
Use ItemGroup to group related items. It sets role="list" and adjusts spacing when items use smaller sizes.
Copy/Paster
Creator of shadcn
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).
Everyday tasks and UI generation.
Advanced thinking or reasoning.
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>
);
}
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>
);
}
| Prop | Type | Default |
|---|---|---|
| variant | "default" | "secondary" | "tertiary" | "outline" | "muted" | "transparent" | "default" |
| size | "default" | "sm" | "xs" | "default" |
| render | React.ReactElement | — |
Container with role="list" and vertical spacing. Accepts standard div props.
Horizontal separator between items, built on the Separator component.
| Prop | Type | Default |
|---|---|---|
| variant | "default" | "icon" | "image" | "default" |