# Link list (Caseroom component)

Links as rows with a line under the title and a chevron; the whole row is the target. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

29% of the 2,062 sites in The UI Index have a list (604 sites).

## Install

- React: `npx shadcn@latest add https://www.themes.dev/caseroom/r/link-list.json`
- Astro: `npx shadcn@latest add https://www.themes.dev/caseroom/r/astro/link-list.json`
- HTML: https://www.themes.dev/caseroom/r/html/link-list.html (with https://www.themes.dev/caseroom/r/html/caseroom.css)

Page: https://www.themes.dev/caseroom/components/link-list/ · Rules for agents: https://www.themes.dev/caseroom/AGENT.md

## Code

### link-list.astro

```astro
---
import { cn } from '@/lib/utils';
import { CHEVRON_PATH, linkList, type LinkItem } from './link-list.variants';

interface Props {
  items: LinkItem[];
  /** Names the list for screen readers when there is no heading right above it. */
  label?: string;
  class?: string;
}
const { items, label, class: className } = Astro.props;
---

<ul aria-label={label} class={cn(linkList.root, className)}>
  {
    items.map((it) => (
      <li>
        <a href={it.href} class={linkList.link}>
          <span class={linkList.text}>
            <span class={linkList.title}>{it.title}</span>
            {it.description && <span class={linkList.description}>{it.description}</span>}
          </span>
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class={linkList.chevron}><path d={CHEVRON_PATH} /></svg>
        </a>
      </li>
    ))
  }
</ul>
```

### link-list.tsx

```tsx
import { cn } from '@/lib/utils';
import { CHEVRON_PATH, linkList, type LinkItem } from './link-list.variants';

export type { LinkItem };

export interface LinkListProps {
  items: LinkItem[];
  /** Names the list for screen readers when there is no heading right above it. */
  label?: string;
  className?: string;
}

/** Links as rows with a chevron; the whole row is the target. */
export function LinkList({ items, label, className }: LinkListProps) {
  return (
    <ul aria-label={label} className={cn(linkList.root, className)}>
      {items.map((it) => (
        <li key={it.href + it.title}>
          <a href={it.href} className={linkList.link}>
            <span className={linkList.text}>
              <span className={linkList.title}>{it.title}</span>
              {it.description && <span className={linkList.description}>{it.description}</span>}
            </span>
            <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={linkList.chevron}><path d={CHEVRON_PATH} /></svg>
          </a>
        </li>
      ))}
    </ul>
  );
}
```

### link-list.variants.ts

```tsx
/** A list of links as rows (the "chevron link list"): a title, an optional line under it, a chevron that moves on
 *  hover. Rows are separated by hairlines: it is a list of destinations, like table rows. The whole row is the
 *  target. */
export const linkList = {
  root: 'm-0 w-full list-none divide-y rounded-lg border p-0',
  link: 'group flex min-h-14 items-center gap-4 px-4 py-3 transition-colors first:rounded-t-lg last:rounded-b-lg hover:bg-accent active:bg-pressed',
  text: 'min-w-0 flex-1',
  title: 'block text-sm font-medium',
  description: 'mt-0.5 block text-sm text-pretty text-muted-foreground',
  chevron: 'size-4 shrink-0 text-muted-foreground transition-transform duration-150 group-hover:translate-x-0.5 group-hover:text-foreground',
};
export type LinkItem = { title: string; href: string; description?: string };
export const CHEVRON_PATH = 'M6 3.5l4.5 4.5L6 12.5';
```
