# Item

A row in a list, with optional media and actions.

Source: [item.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/item.tsx)

```tsx
import { FileTextIcon } from "lucide-react";

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

export default function ItemVariants() {
  return (
    <ItemGroup className="max-w-lg">
      <Item>
        <ItemMedia variant="icon">
          <FileTextIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Quarterly report.pdf</ItemTitle>
          <ItemDescription>2.4 MB · Uploaded by Ada Park</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="secondary" size="sm">
            Download
          </Button>
        </ItemActions>
      </Item>
      <Item variant="outline">
        <ItemMedia variant="icon">
          <FileTextIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Brand guidelines.pdf</ItemTitle>
          <ItemDescription>8.1 MB · Uploaded by Sam Rivera</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="secondary" size="sm">
            Download
          </Button>
        </ItemActions>
      </Item>
    </ItemGroup>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/item
```

## Usage

```tsx
import { FileTextIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemFooter,
  ItemGroup,
  ItemHeader,
  ItemMedia,
  ItemSeparator,
  ItemTitle,
} from "@/components/ui/item";

export function FileList() {
  return (
    <ItemGroup>
      <Item variant="outline">
        <ItemHeader>Shared with you</ItemHeader>
        <ItemMedia variant="icon">
          <FileTextIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Quarterly report.pdf</ItemTitle>
          <ItemDescription>2.4 MB</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="secondary" size="sm">
            Download
          </Button>
        </ItemActions>
        <ItemFooter>Updated yesterday</ItemFooter>
      </Item>
      <ItemSeparator />
      <Item variant="outline">
        <ItemContent>
          <ItemTitle>Brand guidelines.pdf</ItemTitle>
        </ItemContent>
      </Item>
    </ItemGroup>
  );
}
```

Long titles and descriptions are truncated.

## Composition

- `ItemGroup`
  - `Item`
    - `ItemHeader`
    - `ItemMedia`
    - `ItemContent`
      - `ItemTitle`
      - `ItemDescription`
    - `ItemActions`
    - `ItemFooter`
  - `ItemSeparator`

## Examples

### Sizes

`size` sets the padding and gaps.

```tsx
import { Badge } from "@/components/ui/badge";
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item";
import { people } from "@/examples/data";

export default function ItemSizes() {
  const sam = people[1];
  return (
    <ItemGroup className="max-w-lg">
      <Item variant="outline">
        <ItemMedia variant="image">
          <img src={sam.image} alt="" />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>{sam.name}</ItemTitle>
          <ItemDescription>{sam.email}</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Badge variant="muted">Admin</Badge>
        </ItemActions>
      </Item>
      <Item variant="outline" size="sm">
        <ItemMedia variant="image">
          <img src={sam.image} alt="" />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>{sam.name}</ItemTitle>
          <ItemDescription>{sam.email}</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Badge variant="muted">Admin</Badge>
        </ItemActions>
      </Item>
      <Item variant="outline" size="xs">
        <ItemMedia variant="image">
          <img src={sam.image} alt="" />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>{sam.name}</ItemTitle>
        </ItemContent>
        <ItemActions>
          <Badge variant="muted">Admin</Badge>
        </ItemActions>
      </Item>
    </ItemGroup>
  );
}
```

### As a link

Pass an element to `render` to make the whole item a link or a button.

```tsx
import { ChevronRightIcon, RocketIcon } from "lucide-react";

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

export default function ItemLink() {
  return (
    <Item variant="outline" className="max-w-lg" render={<a href="#production" />}>
      <ItemMedia variant="icon">
        <RocketIcon />
      </ItemMedia>
      <ItemContent>
        <ItemTitle>Production deployment</ItemTitle>
        <ItemDescription>Promoted from main by Ada Park</ItemDescription>
      </ItemContent>
      <ItemActions>
        <ChevronRightIcon className="size-4 text-icon-muted" />
      </ItemActions>
    </Item>
  );
}
```

### Header and footer

`ItemHeader` and `ItemFooter` span the item's full width.

```tsx
import { GitBranchIcon } from "lucide-react";

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

export default function ItemHeaderFooter() {
  return (
    <Item variant="outline" className="max-w-lg">
      <ItemHeader>
        <Status variant="success">Ready</Status>
        <span className="text-xs text-faint">3 min ago</span>
      </ItemHeader>
      <ItemContent>
        <ItemTitle>website-redesign</ItemTitle>
        <ItemDescription>Fix pricing table alignment on small screens</ItemDescription>
      </ItemContent>
      <ItemFooter>
        <span className="flex items-center gap-1.5 text-xs text-faint">
          <GitBranchIcon className="size-3.5" />
          feature/pricing
        </span>
        <span className="font-mono text-xs text-faint">a1b2c3d</span>
      </ItemFooter>
    </Item>
  );
}
```

### Settings list

Give each control an `aria-label`, since the item's title doesn't label it.

```tsx
import { BellIcon, CreditCardIcon, LanguagesIcon, PaletteIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";

const appearances = { light: "Light", dark: "Dark", system: "System" };

export default function ItemSettingsList() {
  return (
    <ItemGroup className="max-w-lg gap-0 overflow-hidden rounded-md border-[0.5px] border-border-muted *:data-[slot=item]:rounded-none [&>[data-slot=item]:not(:last-child)]:border-b-border-muted">
      <Item size="sm">
        <ItemMedia variant="icon">
          <BellIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Notifications</ItemTitle>
          <ItemDescription>Email and push alerts for mentions</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Switch defaultChecked aria-label="Notifications" />
        </ItemActions>
      </Item>
      <Item size="sm">
        <ItemMedia variant="icon">
          <PaletteIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Appearance</ItemTitle>
          <ItemDescription>Light, dark or match your system</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Select items={appearances} defaultValue="system">
            <SelectTrigger size="sm" aria-label="Appearance">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              {Object.entries(appearances).map(([value, label]) => (
                <SelectItem key={value} value={value}>
                  {label}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </ItemActions>
      </Item>
      <Item size="sm">
        <ItemMedia variant="icon">
          <LanguagesIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Language</ItemTitle>
          <ItemDescription>English (United States)</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="secondary" size="sm">
            Change
          </Button>
        </ItemActions>
      </Item>
      <Item size="sm">
        <ItemMedia variant="icon">
          <CreditCardIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Billing address</ItemTitle>
          <ItemDescription>100 Market Street, San Francisco, CA</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="secondary" size="sm">
            Edit
          </Button>
        </ItemActions>
      </Item>
    </ItemGroup>
  );
}
```

### With separators

`ItemSeparator` draws a line between items. Set the group's gap to zero when using it.

```tsx
import { PlusIcon } from "lucide-react";
import { Fragment } from "react";

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

export default function ItemSeparators() {
  return (
    <ItemGroup className="max-w-lg gap-0">
      {people.slice(0, 4).map((person, index) => (
        <Fragment key={person.value}>
          {index > 0 && <ItemSeparator className="my-0" />}
          <Item size="sm">
            <ItemMedia>
              <Avatar>
                <AvatarImage src={person.image} alt="" />
                <AvatarFallback>{person.initials}</AvatarFallback>
              </Avatar>
            </ItemMedia>
            <ItemContent>
              <ItemTitle>{person.name}</ItemTitle>
              <ItemDescription>{person.email}</ItemDescription>
            </ItemContent>
            <ItemActions>
              <Button variant="ghost" size="icon-sm" aria-label={`Message ${person.name}`}>
                <PlusIcon />
              </Button>
            </ItemActions>
          </Item>
        </Fragment>
      ))}
    </ItemGroup>
  );
}
```

## Props

| Part | Extends |
| --- | --- |
| `ItemGroup` | `<div>` |
| `ItemSeparator` | [`Separator`](https://ui.jarv.is/components/separator.md) |
| `Item` | `<div>` |
| `ItemMedia` | `<div>` |
| `ItemContent` | `<div>` |
| `ItemTitle` | `<div>` |
| `ItemDescription` | `<p>` |
| `ItemActions` | `<div>` |
| `ItemHeader` | `<div>` |
| `ItemFooter` | `<div>` |

The tables leave out `className`, `style` and `render`. Parts built on Base UI take them as its [styling](https://base-ui.com/react/handbook/styling) and [composition](https://base-ui.com/react/handbook/composition) guides describe.

### ItemSeparator

| Prop           | Type                         | Description                                               |
| -------------- | ---------------------------- | --------------------------------------------------------- |
| `orientation?` | `"horizontal" \| "vertical"` | The orientation of the separator. Default: `"horizontal"` |

### Item

| Prop       | Type                        | Description          |
| ---------- | --------------------------- | -------------------- |
| `variant?` | `"default" \| "outline"`    | Default: `"default"` |
| `size?`    | `"default" \| "sm" \| "xs"` | Default: `"default"` |

### ItemMedia

| Prop       | Type                             | Description          |
| ---------- | -------------------------------- | -------------------- |
| `variant?` | `"default" \| "icon" \| "image"` | Default: `"default"` |
