<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# ConversationList

A list of conversations to choose from: each row shows the avatar, the title, when the latest message arrived and a line of it, and marks unread conversations with a bold title and a dot. The open conversation's row is highlighted and marked `aria-current`. Up and Down arrows move between rows, Home and End jump to the first and last, and Enter, Space or a click calls `onSelect`.

```tsx
import { ConversationList } from "@/components/ouro/conversation-list/conversation-list";
```

## Example

```tsx
<ConversationList label="Conversations" items={conversations} selectedId={openId} onSelect={setOpenId} />
```

## Use instead when

- Use `ItemGroup` instead for a static list with nothing open, and `Tabs` for a handful of views that switch in place.

## Props

Type: `ConversationListProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  | Layout only: width, margins, grid placement (`w-80`). Appearance classes warn in development. |
| `density` | `"comfortable" \| "compact"` | `"comfortable"` | `compact` drops the preview line and uses a smaller avatar. |
| `empty` | `ReactNode` | `"Nothing here yet"` | What shows when `items` is empty. |
| `items` (required) | `readonly ConversationListItem[]` |  | The conversations, in the order they show. |
| `label` (required) | `string` |  | What the list is, for screen readers ("Conversations"). Required, so the list has a name. |
| `onSelect` (required) | `(id: string) => void` |  | Called with a row's `id` when it is chosen by a click, a tap, Enter or Space. |
| `ref` | `Ref<HTMLDivElement>` |  | Reaches the rendered element. |
| `selectedId` | `string \| null` |  | The `id` of the open conversation. Its row is highlighted and marked `aria-current`. |
| `unreadLabel` | `string` | `"Unread"` | What screen readers hear for an unread row's dot, for other languages. |

## Native attributes

Also accepts the attributes in React's `HTMLAttributes` (`id`, `aria-*`, event handlers), passed to the rendered element, except those named on this page.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `conversations`: Not a prop. The rows are `items`: `<ConversationList items={conversations}>`.
- `onValueChange`: Not a prop. A chosen row calls `onSelect`: `<ConversationList onSelect={(id) => open(id)}>`.
- `selected`: Not a prop. The open conversation is `selectedId`: `<ConversationList selectedId={openId}>`.
- `value`: Not a prop. The open conversation is `selectedId`: `<ConversationList selectedId={openId}>`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: ConversationList merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `ConversationListItem`

One conversation in a `ConversationList`.

```ts
interface ConversationListItem {
  /** Identifies the conversation; `onSelect` receives it. */
  id: string;
  /** The conversation's name: a person, a group or a topic. The avatar's initials come from it. */
  title: string;
  /** A second line under the title, such as the group a topic belongs to. */
  subtitle?: string | undefined;
  /** The latest message, cut to one line. `density="compact"` hides it. */
  preview?: ReactNode;
  /** When the latest message arrived, already formatted ("9:41", `<RelativeTime>`). */
  time?: ReactNode;
  /** There are messages the person has not read: the title is bold and a dot shows. */
  unread?: boolean | undefined;
  /** Small marks after the title, such as a `<Badge>`. */
  badges?: ReactNode;
  /** The avatar's picture URL. Without it the avatar shows initials; `false` hides the avatar. */
  avatar?: string | false | undefined;
}
```

[^layout-classes]: test:registry/ouro/components/ouro/conversation-list/conversation-list.test.tsx::ConversationList > forwards a ref and lets app layout classes win verify:testExists(registry/ouro/components/ouro/conversation-list/conversation-list.test.tsx::ConversationList > forwards a ref and lets app layout classes win)
