# Item Inset Separator

> A divider under an Item that starts at its content, past the media, placed with CSS anchor positioning

A React component for projects built on shadcn/ui. The [interactive docs](https://alexandrejoly.me/components/item-inset-separator) show each example live.

```tsx
import { FileTextIcon, ImageIcon, MusicNoteIcon } from "@phosphor-icons/react";
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item";
import { ItemInsetSeparator } from "@/components/ui/item-inset-separator";

const FILES = [
  { icon: FileTextIcon, name: "Quarterly report.pdf", size: "2.4 MB" },
  { icon: ImageIcon, name: "Team offsite.jpg", size: "5.1 MB" },
  { icon: MusicNoteIcon, name: "Launch teaser.mp3", size: "3.8 MB" },
];

export const ItemInsetSeparatorDemo = () => (
  <div className="flex max-w-sm flex-col">
    {FILES.map(({ icon: Icon, name, size }, index) => (
      <Item key={name}>
        <ItemMedia variant="icon">
          <Icon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>{name}</ItemTitle>
          <ItemDescription>{size}</ItemDescription>
        </ItemContent>
        {index < FILES.length - 1 && <ItemInsetSeparator />}
      </Item>
    ))}
  </div>
);
```

## Installation

With the shadcn CLI:

```bash
pnpm dlx shadcn@latest add @alexandrejoly/item-inset-separator
```

Without the `@alexandrejoly` namespace, pass the registry URL:

```bash
pnpm dlx shadcn@latest add https://alexandrejoly.me/r/item-inset-separator.json
```

Or by hand:

1. Add the shadcn components it builds on:

   ```bash
   pnpm dlx shadcn@latest add item
   ```
2. Copy the source into `components/ui/item-inset-separator.tsx`, then update the import paths to match your aliases:

   ```tsx
   import type * as React from "react";

   import { ItemSeparator } from "@/components/ui/item";
   import { cn } from "@/lib/utils";

   function ItemInsetSeparator({
     className,
     ...props
   }: React.ComponentProps<typeof ItemSeparator>) {
     return (
       <ItemSeparator
         className={cn(
           "[[data-slot=item]:has(>&)]:relative",
           "[[data-slot=item]:has(>&)]:[anchor-scope:--item-content]",
           "[[data-slot=item]:has(>&)>[data-slot=item-content]:not([data-slot=item-content]~*)]:[anchor-name:--item-content]",
           "absolute start-0 end-0 bottom-0 my-0 w-auto!",
           "supports-[anchor-name:--a]:start-[anchor(--item-content_start)]",
           className
         )}
         {...props}
       />
     );
   }

   export { ItemInsetSeparator };
   ```

## Usage

```tsx
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item";
import { ItemInsetSeparator } from "@/components/ui/item-inset-separator";
```

```tsx
<div className="flex flex-col">
  {projects.map((project, index) => (
    <Item key={project.id}>
      <ItemMedia variant="icon">
        <FolderIcon />
      </ItemMedia>
      <ItemContent>
        <ItemTitle>{project.name}</ItemTitle>
        <ItemDescription>{project.description}</ItemDescription>
      </ItemContent>
      {index < projects.length - 1 && <ItemInsetSeparator />}
    </Item>
  ))}
</div>
```

## Examples

### Inset past the media

Each line starts at its own row’s text: past an image, past an icon, or at the edge with no media.

> Starting each line at its text needs CSS anchor positioning. Without that support, the lines run full width.

```tsx
import { HashIcon } from "@phosphor-icons/react";
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item";
import { ItemInsetSeparator } from "@/components/ui/item-inset-separator";

export const ItemInsetSeparatorMedia = () => (
  <div className="flex max-w-sm flex-col">
    <Item>
      <ItemMedia variant="image">
        <img alt="" height={40} src="/apple-touch-icon.png" width={40} />
      </ItemMedia>
      <ItemContent>
        <ItemTitle>Alexandre Joly</ItemTitle>
        <ItemDescription>Started a thread in #design</ItemDescription>
      </ItemContent>
      <ItemInsetSeparator />
    </Item>
    <Item>
      <ItemMedia variant="icon">
        <HashIcon />
      </ItemMedia>
      <ItemContent>
        <ItemTitle>#design</ItemTitle>
        <ItemDescription>12 new messages</ItemDescription>
      </ItemContent>
      <ItemInsetSeparator />
    </Item>
    <Item>
      <ItemContent>
        <ItemTitle>Weekly digest</ItemTitle>
        <ItemDescription>Sent every Monday at 9:00</ItemDescription>
      </ItemContent>
    </Item>
  </div>
);
```

## Accessibility

Each line has the `separator` role, so a screen reader announces one inside every row. The rows already part the content: pass `aria-hidden` to keep the line visual only.

## API reference

### ItemInsetSeparator

Placed inside an `Item`, a line along its bottom from where `ItemContent` starts, so it follows each row’s media, or the row’s edge without one. Render it in every row but the last, and stack the rows without a gap, in a plain flex column rather than an `ItemGroup`, so each line sits midway between two rows. A plain class replaces its position or margins; its start and width need an important class, such as `start-4!` or `w-1/2!`.

Takes every prop of [shadcn ItemSeparator](https://ui.shadcn.com/docs/components/base/item).

[All components](https://alexandrejoly.me/components.md)
