---
title: Item
description: Flexible row for lists, menus, and settings.
group: Components
order: 42
---

Arrange media, a title, supporting text, and actions in one row. Stack items with `ItemGroup` to build a list.

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

function BadgeCheckIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" />
      <path d="m9 12 2 2 4-4" />
    </svg>
  )
}

function ChevronRightIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="m9 18 6-6-6-6" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <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="#" />}>
        <ItemMedia>
          <BadgeCheckIcon className="size-5" />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Your profile has been verified.</ItemTitle>
        </ItemContent>
        <ItemActions>
          <ChevronRightIcon className="size-4" />
        </ItemActions>
      </Item>
    </div>
  )
}
```

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add item
```

## Usage

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

```tsx
<Item>
  <ItemMedia variant="icon">
    <Icon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Title</ItemTitle>
    <ItemDescription>Description</ItemDescription>
  </ItemContent>
  <ItemActions>
    <Button>Action</Button>
  </ItemActions>
</Item>
```

## Composition

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

## Variant

Use `variant` to choose a default, outlined, or muted surface.

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

export function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <Item>
        <ItemContent>
          <ItemTitle>Default</ItemTitle>
          <ItemDescription>
            Transparent background with no border.
          </ItemDescription>
        </ItemContent>
      </Item>
      <Item variant="outline">
        <ItemContent>
          <ItemTitle>Outline</ItemTitle>
          <ItemDescription>Outlined style with a visible border.</ItemDescription>
        </ItemContent>
      </Item>
      <Item variant="muted">
        <ItemContent>
          <ItemTitle>Muted</ItemTitle>
          <ItemDescription>
            Muted background for secondary content.
          </ItemDescription>
        </ItemContent>
      </Item>
    </div>
  )
}
```

## Size

`size` accepts `default`, `sm`, or `xs`.

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

function InboxIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <polyline points="22 12 16 12 14 15 10 15 8 12 2 12" />
      <path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <Item variant="outline">
        <ItemMedia variant="icon">
          <InboxIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Default</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>
  )
}
```

## Icon

Set `variant="icon"` on `ItemMedia` for an icon tile.

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

function ShieldAlertIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />
      <path d="M12 8v4" />
      <path d="M12 16h.01" />
    </svg>
  )
}

export function Example() {
  return (
    <Item variant="outline" className="max-w-lg">
      <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>
  )
}
```

## Avatar

Place an `Avatar` inside `ItemMedia`.

```tsx
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 Example() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-4">
      <Item variant="outline">
        <ItemMedia>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-1.png" alt="Mina Cho" />
            <AvatarFallback>MC</AvatarFallback>
          </Avatar>
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Mina Cho</ItemTitle>
          <ItemDescription>Last seen 5 months ago</ItemDescription>
        </ItemContent>
      </Item>
      <Item variant="outline">
        <ItemMedia>
          <div className="flex -space-x-2">
            <Avatar size="sm" className="ring-2 ring-background">
              <AvatarImage src="/assets/brand/demos/avatars/avatar-2.png" alt="Jonas Berg" />
              <AvatarFallback>JB</AvatarFallback>
            </Avatar>
            <Avatar size="sm" className="ring-2 ring-background">
              <AvatarImage src="/assets/brand/demos/avatars/avatar-3.png" alt="Elsa Lind" />
              <AvatarFallback>EL</AvatarFallback>
            </Avatar>
            <Avatar size="sm" className="ring-2 ring-background">
              <AvatarImage src="/assets/brand/demos/avatars/avatar-4.png" alt="Noah Park" />
              <AvatarFallback>NP</AvatarFallback>
            </Avatar>
          </div>
        </ItemMedia>
        <ItemContent>
          <ItemTitle>No members</ItemTitle>
          <ItemDescription>Invite people to this project.</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button size="sm" variant="outline">
            Invite
          </Button>
        </ItemActions>
      </Item>
    </div>
  )
}
```

## Image

Set `variant="image"` on `ItemMedia` for cover art or thumbnails.

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

const tracks = [
  {
    title: "Midnight City Lights",
    description: "Electric Nights",
    image: "/assets/brand/demos/attachments/attachment-1.png",
    duration: "3:45",
  },
  {
    title: "Coffee Shop Conversations",
    description: "Urban Stories",
    image: "/assets/brand/demos/attachments/attachment-2.png",
    duration: "4:05",
  },
  {
    title: "Digital Rain",
    description: "Binary Beats",
    image: "/assets/brand/demos/attachments/attachment-3.png",
    duration: "3:30",
  },
] as const

export function Example() {
  return (
    <ItemGroup className="max-w-md gap-4">
      {tracks.map((track) => (
        <Item key={track.title} variant="outline">
          <ItemMedia variant="image">
            <img src={track.image} alt={track.title} />
          </ItemMedia>
          <ItemContent>
            <div className="flex items-center justify-between gap-2">
              <ItemTitle>{track.title}</ItemTitle>
              <span className="shrink-0 text-sm text-muted-foreground">
                {track.duration}
              </span>
            </div>
            <ItemDescription>{track.description}</ItemDescription>
          </ItemContent>
        </Item>
      ))}
    </ItemGroup>
  )
}
```

## Group

Group related rows with `ItemGroup` and, when needed, `ItemSeparator`.

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemSeparator,
  ItemTitle,
} from "@/components/ui/item"

const people = [
  {
    name: "Mina Cho",
    email: "mina@arctis.dev",
    src: "/assets/brand/demos/avatars/avatar-1.png",
    fallback: "MC",
  },
  {
    name: "Jonas Berg",
    email: "jonas@arctis.dev",
    src: "/assets/brand/demos/avatars/avatar-2.png",
    fallback: "JB",
  },
  {
    name: "Elsa Lind",
    email: "elsa@arctis.dev",
    src: "/assets/brand/demos/avatars/avatar-3.png",
    fallback: "EL",
  },
] as const

export function Example() {
  return (
    <ItemGroup className="max-w-md rounded-md border border-border">
      {people.map((person, index) => (
        <div key={person.email}>
          {index > 0 ? <ItemSeparator /> : null}
          <Item>
            <ItemMedia>
              <Avatar size="sm">
                <AvatarImage src={person.src} alt={person.name} />
                <AvatarFallback>{person.fallback}</AvatarFallback>
              </Avatar>
            </ItemMedia>
            <ItemContent>
              <ItemTitle>{person.name}</ItemTitle>
              <ItemDescription>{person.email}</ItemDescription>
            </ItemContent>
          </Item>
        </div>
      ))}
    </ItemGroup>
  )
}
```

## Header

Add `ItemHeader` above the content for card-style layouts.

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

const models = [
  {
    name: "v0-1.5-sm",
    description: "Everyday tasks and UI generation.",
    image: "/assets/brand/demos/attachments/attachment-1.png",
  },
  {
    name: "v0-1.5-lg",
    description: "Advanced thinking or reasoning.",
    image: "/assets/brand/demos/attachments/attachment-2.png",
  },
  {
    name: "v0-2.0-mini",
    description: "Open source model for everyone.",
    image: "/assets/brand/demos/attachments/attachment-3.png",
  },
] as const

export function Example() {
  return (
    <div className="grid w-full max-w-2xl gap-4 sm:grid-cols-3">
      {models.map((model) => (
        <Item
          key={model.name}
          variant="outline"
          className="flex-col flex-nowrap items-stretch gap-3"
        >
          <ItemHeader className="block">
            <img
              src={model.image}
              alt={model.name}
              className="aspect-[4/3] w-full rounded-sm object-cover"
            />
          </ItemHeader>
          <ItemContent className="w-full flex-none">
            <ItemTitle>{model.name}</ItemTitle>
            <ItemDescription>{model.description}</ItemDescription>
          </ItemContent>
        </Item>
      ))}
    </div>
  )
}
```

## Link

Pass `render` to make the item a real `<a>` or `<button>`.

```tsx
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemTitle,
} from "@/components/ui/item"

function ChevronRightIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="m9 18 6-6-6-6" />
    </svg>
  )
}

function ExternalLinkIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M15 3h6v6" />
      <path d="M10 14 21 3" />
      <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <Item render={<a href="#" />}>
        <ItemContent>
          <ItemTitle>Visit our documentation</ItemTitle>
          <ItemDescription>
            Learn how to get started with our components.
          </ItemDescription>
        </ItemContent>
        <ItemActions>
          <ChevronRightIcon className="size-4" />
        </ItemActions>
      </Item>
      <Item
        variant="outline"
        render={<a href="https://example.com" target="_blank" rel="noreferrer noopener" />}
      >
        <ItemContent>
          <ItemTitle>External resource</ItemTitle>
          <ItemDescription>
            Opens in a new tab with security attributes.
          </ItemDescription>
        </ItemContent>
        <ItemActions>
          <ExternalLinkIcon className="size-4" />
        </ItemActions>
      </Item>
    </div>
  )
}
```

```tsx
<Item render={<a href="/dashboard" />}>
  <ItemContent>
    <ItemTitle>Dashboard</ItemTitle>
    <ItemDescription>Overview of your account and activity.</ItemDescription>
  </ItemContent>
</Item>
```

## API Reference

### Item

Root row. `itemVariants` is also exported for composing styles. Standard HTML attributes pass through to the root or the element provided through `render`.

| Prop | Type | Default |
| --- | --- | --- |
| variant | "default" \| "outline" \| "muted" | "default" |
| size | "default" \| "sm" \| "xs" | "default" |
| render | ReactElement | — |
| className | string | — |

```tsx
<Item variant="outline" size="sm">
  <ItemContent>
    <ItemTitle>Title</ItemTitle>
  </ItemContent>
</Item>
```

### ItemGroup

Groups related items. Renders with `role="list"`.

```tsx
<ItemGroup>
  <Item />
  <Item />
</ItemGroup>
```

### ItemSeparator

Separator between items in a group.

```tsx
<ItemGroup>
  <Item />
  <ItemSeparator />
  <Item />
</ItemGroup>
```

### ItemMedia

Media slot for icons, images, or avatars.

| Prop | Type | Default |
| --- | --- | --- |
| variant | "default" \| "icon" \| "image" | "default" |
| className | string | — |

```tsx
<ItemMedia variant="icon">
  <Icon />
</ItemMedia>
```

```tsx
<ItemMedia variant="image">
  <img src="..." alt="..." />
</ItemMedia>
```

### ItemContent

Wraps the title and description.

```tsx
<ItemContent>
  <ItemTitle>Title</ItemTitle>
  <ItemDescription>Description</ItemDescription>
</ItemContent>
```

### ItemTitle

Primary label for the item.

```tsx
<ItemTitle>Item Title</ItemTitle>
```

### ItemDescription

Supporting text. Clamps to two lines.

```tsx
<ItemDescription>Item description</ItemDescription>
```

### ItemActions

Container for buttons or other actions.

```tsx
<ItemActions>
  <Button>Action</Button>
</ItemActions>
```

### ItemHeader

Full-width header above the content.

```tsx
<Item>
  <ItemHeader>Header</ItemHeader>
  <ItemContent>...</ItemContent>
</Item>
```

### ItemFooter

Full-width footer below the content.

```tsx
<Item>
  <ItemContent>...</ItemContent>
  <ItemFooter>Footer</ItemFooter>
</Item>
```

## Source

```tsx
import {
  cloneElement,
  isValidElement,
  type ComponentProps,
  type HTMLAttributes,
  type ReactElement,
  type ReactNode,
} from "react";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";

type ItemVariant = "default" | "outline" | "muted";
type ItemSize = "default" | "sm" | "xs";
type ItemMediaVariant = "default" | "icon" | "image";

const itemVariantClass: Record<ItemVariant, string> = {
  default: "border-transparent bg-transparent",
  outline: "border-border bg-transparent",
  muted: "border-transparent bg-muted/50",
};

const itemSizeClass: Record<ItemSize, string> = {
  default: "gap-4 p-4",
  sm: "gap-2.5 px-4 py-3",
  xs: "gap-2 px-2.5 py-2",
};

function itemVariants({
  variant = "default",
  size = "default",
  className,
}: {
  variant?: ItemVariant;
  size?: ItemSize;
  className?: string;
} = {}) {
  return cn(
    "group/item flex w-full flex-wrap items-center rounded-md border text-sm tracking-wide transition-colors duration-100 [&:is(a)]:transition-colors [&:is(a)]:hover:bg-muted",
    itemVariantClass[variant],
    itemSizeClass[size],
    className,
  );
}

const itemMediaVariantClass: Record<ItemMediaVariant, string> = {
  default: "bg-transparent",
  icon: "size-8 rounded-sm border border-border bg-muted [&_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",
};

type ItemProps = HTMLAttributes<HTMLElement> & {
  variant?: ItemVariant;
  size?: ItemSize;
  render?: ReactElement;
  children?: ReactNode;
};

function Item({
  className,
  variant = "default",
  size = "default",
  render,
  children,
  ...props
}: ItemProps) {
  const classes = itemVariants({ variant, size, className });

  if (isValidElement(render)) {
    const element = render as ReactElement<{
      className?: string;
      children?: ReactNode;
    }>;

    return cloneElement(element, {
      ...element.props,
      ...props,
      "data-slot": "item",
      "data-variant": variant,
      "data-size": size,
      className: cn(classes, element.props.className),
      children: children ?? element.props.children,
    } as never);
  }

  return (
    <div
      data-slot="item"
      data-variant={variant}
      data-size={size}
      className={classes}
      {...props}
    >
      {children}
    </div>
  );
}

function ItemGroup({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      role="list"
      data-slot="item-group"
      className={cn(
        "group/item-group flex w-full flex-col",
        className,
      )}
      {...props}
    />
  );
}

function ItemSeparator({
  className,
  ...props
}: ComponentProps<typeof Separator>) {
  return (
    <Separator
      data-slot="item-separator"
      orientation="horizontal"
      className={cn("my-0", className)}
      {...props}
    />
  );
}

function ItemMedia({
  className,
  variant = "default",
  ...props
}: ComponentProps<"div"> & { variant?: ItemMediaVariant }) {
  return (
    <div
      data-slot="item-media"
      data-variant={variant}
      className={cn(
        "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",
        itemMediaVariantClass[variant],
        className,
      )}
      {...props}
    />
  );
}

function ItemContent({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      data-slot="item-content"
      className={cn(
        "flex min-w-0 flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
        className,
      )}
      {...props}
    />
  );
}

function ItemTitle({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      data-slot="item-title"
      className={cn(
        "line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium",
        className,
      )}
      {...props}
    />
  );
}

function ItemDescription({ className, ...props }: ComponentProps<"p">) {
  return (
    <p
      data-slot="item-description"
      className={cn(
        "line-clamp-2 text-left text-sm leading-normal font-normal tracking-wide text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-foreground",
        className,
      )}
      {...props}
    />
  );
}

function ItemActions({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      data-slot="item-actions"
      className={cn("flex items-center gap-2", className)}
      {...props}
    />
  );
}

function ItemHeader({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      data-slot="item-header"
      className={cn(
        "flex w-full items-center justify-between gap-2",
        className,
      )}
      {...props}
    />
  );
}

function ItemFooter({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      data-slot="item-footer"
      className={cn(
        "flex w-full items-center justify-between gap-2",
        className,
      )}
      {...props}
    />
  );
}

export {
  Item,
  ItemMedia,
  ItemContent,
  ItemActions,
  ItemGroup,
  ItemSeparator,
  ItemTitle,
  ItemDescription,
  ItemHeader,
  ItemFooter,
  itemVariants,
};
export type { ItemVariant, ItemSize, ItemMediaVariant };
```
