Item
A versatile component for displaying content with media, title, description, and actions.
<div class="flex w-full max-w-md flex-col gap-6">
<twig:Item variant="outline">
<twig:Item:Content>
<twig:Item:Title>Basic Item</twig:Item:Title>
<twig:Item:Description>
A simple item with title and description.
</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button variant="outline" size="sm">
Action
</twig:Button>
</twig:Item:Actions>
</twig:Item>
<twig:Item variant="outline" size="sm" as="a" href="#">
<twig:Item:Media>
<twig:ux:icon name="lucide:badge-check" class="size-5" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Your profile has been verified.</twig:Item:Title>
</twig:Item:Content>
<twig:Item:Actions>
<twig:ux:icon name="lucide:chevron-right" class="size-4" />
</twig:Item:Actions>
</twig:Item>
</div>
Installation
bin/console ux:install item --kit shadcn
That's it!
Install the following Composer dependencies:
composer require twig/extra-bundle twig/html-extra:^3.12.0 tales-from-a-dev/twig-tailwind-extra:^1.0.0
Copy the following file(s) into your Symfony app:
{# @prop variant 'default'|'outline'|'muted' The visual style variant. Defaults to `default` #}
{# @prop size 'default'|'sm'|'xs' The item size. Defaults to `default` #}
{# @prop as 'div' The HTML tag to render. Defaults to `div` #}
{# @block content The item content, typically includes `Item:Media`, `Item:Content`, and/or `Item:Actions` #}
{%- props variant = 'default', size = 'default', as = 'div' -%}
{%- set style = html_cva(
base: 'group/item flex w-full flex-wrap items-center rounded-lg border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted',
variants: {
variant: {
default: 'border-transparent',
outline: 'border-border',
muted: 'border-transparent bg-muted/50',
},
size: {
default: 'gap-2.5 px-3 py-2.5',
sm: 'gap-2.5 px-3 py-2.5',
xs: 'gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0',
},
},
default_variant: {
variant: 'default',
size: 'default',
},
) -%}
<{{ as }}
data-slot="item"
data-variant="{{ variant }}"
data-size="{{ size }}"
class="{{ style.apply({variant: variant, size: size}, attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</{{ as }}>
{# @block content The action buttons or controls for the item #}
<div
data-slot="item-actions"
class="{{ ('flex items-center gap-2 ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
{# @block content The main content area, typically includes `Item:Title` and `Item:Description` #}
<div
data-slot="item-content"
class="{{ ('flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
{# @block content The descriptive text of the item #}
<p
data-slot="item-description"
class="{{ ('line-clamp-2 text-left rtl:text-start text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</p>
{# @block content The footer area, typically contains metadata or secondary actions #}
<div
data-slot="item-footer"
class="{{ ('flex basis-full items-center justify-between gap-2 ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
{# @block content The grouped items, typically multiple `Item` components #}
<div
role="list"
data-slot="item-group"
class="{{ ('group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2 ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
{# @block content The header area, typically contains title and actions #}
<div
data-slot="item-header"
class="{{ ('flex basis-full items-center justify-between gap-2 ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
{# @prop variant 'default'|'icon'|'image' The media display style. Defaults to `default` #}
{# @block content The visual element, typically an icon or image #}
{%- props variant = 'default' -%}
{%- set style = html_cva(
base: 'flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none',
variants: {
variant: {
default: 'bg-transparent',
icon: "[&_svg:not([class*='size-'])]:size-4",
image: 'size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover',
},
},
default_variant: {
variant: 'default',
},
) -%}
<div
data-slot="item-media"
data-variant="{{ variant }}"
class="{{ style.apply({variant: variant}, attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
{# @prop orientation 'horizontal'|'vertical' The separator orientation. Defaults to `horizontal` #}
{# @prop decorative bool Whether the separator is purely decorative (not semantic). Defaults to `true` #}
{# @block content Optional custom separator content #}
{%- props orientation = 'horizontal', decorative = true -%}
<twig:Separator
orientation="{{ orientation }}"
decorative="{{ decorative }}"
class="{{ ('my-2 ' ~ attributes.render('class'))|tailwind_merge }}"
{{ ...attributes }}
/>
{# @block content The title text of the item #}
<div
data-slot="item-title"
class="{{ ('line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4 ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
{# @prop orientation 'horizontal'|'vertical' The separator orientation. Defaults to `horizontal` #}
{# @prop decorative boolean Whether the separator is purely decorative (not semantic). Defaults to `true` #}
{%- props orientation = 'horizontal', decorative = true -%}
<div
class="{{ ('shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({
'data-slot': 'separator',
'data-orientation': orientation,
role: decorative ? 'none' : 'separator',
'aria-orientation': decorative ? false : orientation,
}) }}
></div>
Happy coding!
Usage
<twig:Item>
<twig:Item:Media variant="icon">
<twig:ux:icon name="lucide:inbox" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Title</twig:Item:Title>
<twig:Item:Description>Description</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button>Action</twig:Button>
</twig:Item:Actions>
</twig:Item>
Examples
Variant
Use the variant prop to change the visual style of the item.
<div class="flex w-full max-w-md flex-col gap-6">
<twig:Item>
<twig:Item:Media variant="icon">
<twig:ux:icon name="lucide:inbox" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Default Variant</twig:Item:Title>
<twig:Item:Description>
Transparent background with no border.
</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
<twig:Item variant="outline">
<twig:Item:Media variant="icon">
<twig:ux:icon name="lucide:inbox" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Outline Variant</twig:Item:Title>
<twig:Item:Description>
Outlined style with a visible border.
</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
<twig:Item variant="muted">
<twig:Item:Media variant="icon">
<twig:ux:icon name="lucide:inbox" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Muted Variant</twig:Item:Title>
<twig:Item:Description>
Muted background for secondary content.
</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
</div>
Size
Use the size prop to change the size of the item. Available sizes are default, sm, and xs.
<div class="flex w-full max-w-md flex-col gap-6">
<twig:Item variant="outline">
<twig:Item:Media variant="icon">
<twig:ux:icon name="lucide:inbox" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Default Size</twig:Item:Title>
<twig:Item:Description>
The standard size for most use cases.
</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
<twig:Item variant="outline" size="sm">
<twig:Item:Media variant="icon">
<twig:ux:icon name="lucide:inbox" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Small Size</twig:Item:Title>
<twig:Item:Description>A compact size for dense layouts.</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
<twig:Item variant="outline" size="xs">
<twig:Item:Media variant="icon">
<twig:ux:icon name="lucide:inbox" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Extra Small Size</twig:Item:Title>
<twig:Item:Description>The most compact size available.</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
</div>
Icon
Use Item:Media with variant="icon" to display an icon.
<div class="flex w-full max-w-lg flex-col gap-6">
<twig:Item variant="outline">
<twig:Item:Media variant="icon">
<twig:ux:icon name="lucide:shield-alert" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Security Alert</twig:Item:Title>
<twig:Item:Description>
New login detected from unknown device.
</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button size="sm" variant="outline">
Review
</twig:Button>
</twig:Item:Actions>
</twig:Item>
</div>
Avatar
Use Item:Media to display an avatar.
<div class="flex w-full max-w-lg flex-col gap-6">
<twig:Item variant="outline">
<twig:Item:Media>
<twig:Avatar class="size-10">
<twig:Avatar:Image src="https://github.com/evilrabbit.png" alt="ER" />
<twig:Avatar:Fallback>ER</twig:Avatar:Fallback>
</twig:Avatar>
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Evil Rabbit</twig:Item:Title>
<twig:Item:Description>
Last seen 5 months ago
</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button size="icon-sm" variant="outline" class="rounded-full" aria-label="Invite">
<twig:ux:icon name="lucide:plus" />
</twig:Button>
</twig:Item:Actions>
</twig:Item>
<twig:Item variant="outline">
<twig:Item:Media>
<div class="flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
<twig:Avatar class="hidden sm:flex">
<twig:Avatar:Image src="https://github.com/shadcn.png" alt="@shadcn" />
<twig:Avatar:Fallback>CN</twig:Avatar:Fallback>
</twig:Avatar>
<twig:Avatar class="hidden sm:flex">
<twig:Avatar:Image src="https://github.com/maxleiter.png" alt="@maxleiter" />
<twig:Avatar:Fallback>LR</twig:Avatar:Fallback>
</twig:Avatar>
<twig:Avatar>
<twig:Avatar:Image src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
<twig:Avatar:Fallback>ER</twig:Avatar:Fallback>
</twig:Avatar>
</div>
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>No Team Members</twig:Item:Title>
<twig:Item:Description>
Invite your team to collaborate on this project.
</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button size="sm" variant="outline">
Invite
</twig:Button>
</twig:Item:Actions>
</twig:Item>
</div>
Image
Use Item:Media with variant="image" to display an image.
<div class="flex w-full max-w-md flex-col gap-6">
<twig:Item:Group class="gap-4">
<twig:Item variant="outline" as="a" href="#" role="listitem">
<twig:Item:Media variant="image">
<img src="https://avatar.vercel.sh/Midnight+City+Lights" alt="Midnight City Lights" class="object-cover grayscale" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Midnight City Lights - <span class="text-muted-foreground">Electric Nights</span></twig:Item:Title>
<twig:Item:Description>Neon Dreams</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Content class="flex-none text-center">
<twig:Item:Description>3:45</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
<twig:Item variant="outline" as="a" href="#" role="listitem">
<twig:Item:Media variant="image">
<img src="https://avatar.vercel.sh/Coffee+Shop" alt="Coffee Shop Conversations" class="object-cover grayscale" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Coffee Shop Conversations - <span class="text-muted-foreground">Urban Stories</span></twig:Item:Title>
<twig:Item:Description>The Morning Brew</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Content class="flex-none text-center">
<twig:Item:Description>4:05</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
<twig:Item variant="outline" as="a" href="#" role="listitem">
<twig:Item:Media variant="image">
<img src="https://avatar.vercel.sh/Digital+Rain" alt="Digital Rain" class="object-cover grayscale" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>Digital Rain - <span class="text-muted-foreground">Binary Beats</span></twig:Item:Title>
<twig:Item:Description>Cyber Symphony</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Content class="flex-none text-center">
<twig:Item:Description>3:30</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
</twig:Item:Group>
</div>
Group
Use Item:Group to group related items together.
<twig:Item:Group class="max-w-sm">
<twig:Item variant="outline">
<twig:Item:Media>
<twig:Avatar>
<twig:Avatar:Image src="https://github.com/shadcn.png" alt="@shadcn" class="grayscale" />
<twig:Avatar:Fallback>S</twig:Avatar:Fallback>
</twig:Avatar>
</twig:Item:Media>
<twig:Item:Content class="gap-1">
<twig:Item:Title>shadcn</twig:Item:Title>
<twig:Item:Description>shadcn@vercel.com</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button variant="ghost" size="icon" class="rounded-full">
<twig:ux:icon name="lucide:plus" />
</twig:Button>
</twig:Item:Actions>
</twig:Item>
<twig:Item variant="outline">
<twig:Item:Media>
<twig:Avatar>
<twig:Avatar:Image src="https://github.com/maxleiter.png" alt="@maxleiter" class="grayscale" />
<twig:Avatar:Fallback>M</twig:Avatar:Fallback>
</twig:Avatar>
</twig:Item:Media>
<twig:Item:Content class="gap-1">
<twig:Item:Title>maxleiter</twig:Item:Title>
<twig:Item:Description>maxleiter@vercel.com</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button variant="ghost" size="icon" class="rounded-full">
<twig:ux:icon name="lucide:plus" />
</twig:Button>
</twig:Item:Actions>
</twig:Item>
<twig:Item variant="outline">
<twig:Item:Media>
<twig:Avatar>
<twig:Avatar:Image src="https://github.com/evilrabbit.png" alt="@evilrabbit" class="grayscale" />
<twig:Avatar:Fallback>E</twig:Avatar:Fallback>
</twig:Avatar>
</twig:Item:Media>
<twig:Item:Content class="gap-1">
<twig:Item:Title>evilrabbit</twig:Item:Title>
<twig:Item:Description>evilrabbit@vercel.com</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button variant="ghost" size="icon" class="rounded-full">
<twig:ux:icon name="lucide:plus" />
</twig:Button>
</twig:Item:Actions>
</twig:Item>
</twig:Item:Group>
Header
Use Item:Header to add a header above the item content.
<div class="flex w-full max-w-xl flex-col gap-6">
<twig:Item:Group class="grid grid-cols-3 gap-4">
<twig:Item variant="outline">
<twig:Item:Header>
<img
src="https://images.unsplash.com/photo-1650804068570-7fb2e3dbf888?q=80&w=640&auto=format&fit=crop"
alt="v0-1.5-sm"
class="aspect-square w-full rounded-sm object-cover"
/>
</twig:Item:Header>
<twig:Item:Content>
<twig:Item:Title>v0-1.5-sm</twig:Item:Title>
<twig:Item:Description>Everyday tasks and UI generation.</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
<twig:Item variant="outline">
<twig:Item:Header>
<img
src="https://images.unsplash.com/photo-1610280777472-54133d004c8c?q=80&w=640&auto=format&fit=crop"
alt="v0-1.5-lg"
class="aspect-square w-full rounded-sm object-cover"
/>
</twig:Item:Header>
<twig:Item:Content>
<twig:Item:Title>v0-1.5-lg</twig:Item:Title>
<twig:Item:Description>Advanced thinking or reasoning.</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
<twig:Item variant="outline">
<twig:Item:Header>
<img
src="https://images.unsplash.com/photo-1602146057681-08560aee8cde?q=80&w=640&auto=format&fit=crop"
alt="v0-2.0-mini"
class="aspect-square w-full rounded-sm object-cover"
/>
</twig:Item:Header>
<twig:Item:Content>
<twig:Item:Title>v0-2.0-mini</twig:Item:Title>
<twig:Item:Description>Open Source model for everyone.</twig:Item:Description>
</twig:Item:Content>
</twig:Item>
</twig:Item:Group>
</div>
Link
Use the as prop to render the item as a link. The hover and focus states will be applied to the anchor element.
<div class="flex w-full max-w-md flex-col gap-4">
<twig:Item as="a" href="#">
<twig:Item:Content>
<twig:Item:Title>Visit our documentation</twig:Item:Title>
<twig:Item:Description>
Learn how to get started with our components.
</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:ux:icon name="lucide:chevron-right" class="size-4" />
</twig:Item:Actions>
</twig:Item>
<twig:Item variant="outline" as="a" href="#" target="_blank" rel="noopener noreferrer">
<twig:Item:Content>
<twig:Item:Title>External resource</twig:Item:Title>
<twig:Item:Description>
Opens in a new tab with security attributes.
</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:ux:icon name="lucide:external-link" class="size-4" />
</twig:Item:Actions>
</twig:Item>
</div>
RTL
To enable RTL support, set the dir="rtl" attribute on the root element.
<div class="flex flex-col gap-8 w-full items-center">
{# Arabic #}
<div class="flex w-full max-w-md flex-col gap-6" dir="rtl">
<twig:Item variant="outline" dir="rtl">
<twig:Item:Content>
<twig:Item:Title>عنصر أساسي</twig:Item:Title>
<twig:Item:Description>عنصر بسيط يحتوي على عنوان ووصف.</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button variant="outline" size="sm">إجراء</twig:Button>
</twig:Item:Actions>
</twig:Item>
<twig:Item variant="outline" size="sm" as="a" href="#" dir="rtl">
<twig:Item:Media>
<twig:ux:icon name="lucide:badge-check" class="size-5" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>تم التحقق من ملفك الشخصي.</twig:Item:Title>
</twig:Item:Content>
<twig:Item:Actions>
<twig:ux:icon name="lucide:chevron-right" class="size-4" />
</twig:Item:Actions>
</twig:Item>
</div>
{# Hebrew #}
<div class="flex w-full max-w-md flex-col gap-6" dir="rtl">
<twig:Item variant="outline" dir="rtl">
<twig:Item:Content>
<twig:Item:Title>ערך בסיסי</twig:Item:Title>
<twig:Item:Description>ערך עם כותרת ותיאור.</twig:Item:Description>
</twig:Item:Content>
<twig:Item:Actions>
<twig:Button variant="outline" size="sm">בצע</twig:Button>
</twig:Item:Actions>
</twig:Item>
<twig:Item variant="outline" size="sm" as="a" href="#" dir="rtl">
<twig:Item:Media>
<twig:ux:icon name="lucide:badge-check" class="size-5" />
</twig:Item:Media>
<twig:Item:Content>
<twig:Item:Title>החשבון שלך אומת.</twig:Item:Title>
</twig:Item:Content>
<twig:Item:Actions>
<twig:ux:icon name="lucide:chevron-right" class="size-4" />
</twig:Item:Actions>
</twig:Item>
</div>
</div>
API Reference
Component Item
| Prop | Type | Description |
|---|---|---|
variant |
'default'|'outline'|'muted' |
The visual style variant. Defaults to default |
size |
'default'|'sm'|'xs' |
The item size. Defaults to default |
as |
'div' |
The HTML tag to render. Defaults to div |
| Block | Description |
|---|---|
content |
The item content, typically includes Item:Media, Item:Content, and/or Item:Actions |
Component Item:Actions
| Block | Description |
|---|---|
content |
The action buttons or controls for the item |
Component Item:Content
| Block | Description |
|---|---|
content |
The main content area, typically includes Item:Title and Item:Description |
Component Item:Description
| Block | Description |
|---|---|
content |
The descriptive text of the item |
Component Item:Footer
| Block | Description |
|---|---|
content |
The footer area, typically contains metadata or secondary actions |
Component Item:Group
| Block | Description |
|---|---|
content |
The grouped items, typically multiple Item components |
Component Item:Header
| Block | Description |
|---|---|
content |
The header area, typically contains title and actions |
Component Item:Media
| Prop | Type | Description |
|---|---|---|
variant |
'default'|'icon'|'image' |
The media display style. Defaults to default |
| Block | Description |
|---|---|
content |
The visual element, typically an icon or image |
Component Item:Separator
| Prop | Type | Description |
|---|---|---|
orientation |
'horizontal'|'vertical' |
The separator orientation. Defaults to horizontal |
decorative |
bool |
Whether the separator is purely decorative (not semantic). Defaults to true |
| Block | Description |
|---|---|
content |
Optional custom separator content |
Component Item:Title
| Block | Description |
|---|---|
content |
The title text of the item |