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

# ShowMore

"Show more" for a long list or a long text. It cuts a list to its first items, clamps text to a number of lines, folds content of any kind (a long post with pictures) to a height under a fade, or grows a list a page at a time from the server, and moves focus to the first newly shown item, so keyboard and screen reader users land on what they asked for. A list that loads more also says how many items now show. The button names what it folds (`aria-controls`) and, unless it loads more, says whether it is open (`aria-expanded`). Folded text and content are folded by CSS in the first commit and in server HTML, and the button comes after the browser has laid them out: ShowMore does not guess whether they are longer than the fold. One ResizeObserver reads the layout and adds the button to what overflows. The visible items are in the server HTML. ShowMore draws the list itself: put a border around the list on a wrapping element, and borders between rows on each item.

```tsx
import { ShowMore } from "@/components/ouro/collapsible/show-more";
```

## Example

```tsx
<ShowMore items={reviews.map((review) => <Review key={review.id} {...review} />)} initialCount={3} />
<ShowMore lines={3}>{recipe.description}</ShowMore>
<ShowMore maxHeight={180}><Post body={post.body} /></ShowMore>
<ShowMore items={rows} hasMore={Boolean(nextCursor)} loading={loading} onShowMore={loadNext} href={nextHref} />
<ShowMore lines={3} open={open} onOpenChange={setOpen}>{recipe.description}</ShowMore>
```

## Use instead when

- Use `Collapsible` instead to hide a whole section behind its title.
- Use `Pagination` instead when people need to jump to a page or share its URL.

## Props

The props the modes share. Type: `ShowMoreProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  | Layout only: margins, width, grid placement (`mt-6`). Text classes go on a parent so the items inherit them. |
| `measureKey` | `string \| number` |  | A stable name for what this shows (a message id), for `lines` and `maxHeight`. A row that mounts again with the same key starts from its last measured fit, so it does not mount folded and then unfold. Without it, a mount measures afresh. The browser still measures and corrects it, so a changed width costs one write. |
| `ref` | `Ref<HTMLDivElement>` |  | Reaches the rendered element. |
| `showMoreLabel` | `string` | `"Show more"` | The button's text while there is more to show. Translate it here. |

## Modes

`ShowMoreProps` is a union: pass the props of exactly one mode. Mixing them is a type error.

### Height: `ShowMoreHeightProps`

Long content of any kind folded to `maxHeight` pixels under a fade, with "Show more" and "Show less".

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  | The content: rich text, pictures, cards. Screen readers always read all of it, and moving keyboard focus into the folded part unfolds it, so nothing focusable is out of sight. |
| `estimate` | `ShowMoreEstimate` |  | Not read: ShowMore does not guess whether content is longer than its fold, so this changes nothing. Kept so an app that passes it still compiles. |
| `maxHeight` (required) | `number` |  | How tall the content shows while folded, in CSS pixels, with its last part fading out. Content at most a fifth taller shows whole, since a button that reveals a line or two helps nobody. The button appears only when the content is folded. |
| `onOpenChange` | `((open: boolean) => void)` |  | Called with the new state when the button is pressed, or when keyboard focus moving below a `maxHeight` fold unfolds it, whether or not `open` is set. |
| `open` | `boolean` |  | Whether the content shows in full. With it, the app owns the state: "Show more" and "Show less" ask for the change through `onOpenChange`, and nothing changes until the app sets `open`. Without it, ShowMore keeps its own state, starting folded. |
| `showLessLabel` | `string` | `"Show less"` | The button's text while everything shows. Translate it here. |

Not in this mode (typed `never`):

- `hasMore`: Only with `onShowMore`.
- `href`: Only with `onShowMore`.
- `initialCount`: Only with `items` and no `onShowMore`.
- `items`: List modes only: not with `maxHeight`.
- `lines`: Not with `maxHeight`: fold by height or by lines, not both.
- `loadedLabel`: Only with `onShowMore`.
- `loading`: Only with `onShowMore`.
- `loadingLabel`: Only with `onShowMore`.
- `noMoreLabel`: Only with `onShowMore`.
- `onShowMore`: Only for a list that loads more: with `items`, without `initialCount` or `lines`.
- `ordered`: List modes only: not with `maxHeight`.

### Items: `ShowMoreItemsProps`

A list cut to its first `initialCount` items, with "Show more" and "Show less".

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `initialCount` | `number` | `5` | How many items show while collapsed. |
| `items` (required) | `readonly ReactNode[]` |  | Every item, in order. Each renders in its own list item; give elements a `key`. With `onShowMore`, append each new page here. |
| `onOpenChange` | `((open: boolean) => void)` |  | Called with the new state when the button is pressed, or when keyboard focus moving below a `maxHeight` fold unfolds it, whether or not `open` is set. |
| `open` | `boolean` |  | Whether the content shows in full. With it, the app owns the state: "Show more" and "Show less" ask for the change through `onOpenChange`, and nothing changes until the app sets `open`. Without it, ShowMore keeps its own state, starting folded. |
| `ordered` | `boolean` | `false` (`<ul>`) | Renders an `<ol>`, for a ranked or numbered list. |
| `showLessLabel` | `string` | `"Show less"` | The button's text while everything shows. Translate it here. |

Not in this mode (typed `never`):

- `children`: Text mode only: a list takes `items`.
- `estimate`: Text and height modes only: a list shows its first items.
- `hasMore`: Only with `onShowMore`.
- `href`: Only with `onShowMore`.
- `lines`: 
- `loadedLabel`: Only with `onShowMore`.
- `loading`: Only with `onShowMore`.
- `loadingLabel`: Only with `onShowMore`.
- `maxHeight`: Height mode only: not with `items`.
- `noMoreLabel`: Only with `onShowMore`.
- `onShowMore`: Only for a list that loads more: with `items`, without `initialCount` or `lines`.

### Lines: `ShowMoreLinesProps`

Long text clamped to `lines` lines, with "Show more" and "Show less".

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  | The text. Screen readers always read all of it. |
| `estimate` | `ShowMoreEstimate` |  | Not read: ShowMore does not guess whether content is longer than its fold, so this changes nothing. Kept so an app that passes it still compiles. |
| `lines` (required) | `number` |  | How many lines show while collapsed. The button appears only when the text is longer. |
| `onOpenChange` | `((open: boolean) => void)` |  | Called with the new state when the button is pressed, or when keyboard focus moving below a `maxHeight` fold unfolds it, whether or not `open` is set. |
| `open` | `boolean` |  | Whether the content shows in full. With it, the app owns the state: "Show more" and "Show less" ask for the change through `onOpenChange`, and nothing changes until the app sets `open`. Without it, ShowMore keeps its own state, starting folded. |
| `showLessLabel` | `string` | `"Show less"` | The button's text while everything shows. Translate it here. |

Not in this mode (typed `never`):

- `hasMore`: Only with `onShowMore`.
- `href`: Only with `onShowMore`.
- `initialCount`: Only with `items` and no `onShowMore`.
- `items`: List modes only: not with `lines`.
- `loadedLabel`: Only with `onShowMore`.
- `loading`: Only with `onShowMore`.
- `loadingLabel`: Only with `onShowMore`.
- `maxHeight`: Not with `lines`: fold by height or by lines, not both.
- `noMoreLabel`: Only with `onShowMore`.
- `onShowMore`: Only for a list that loads more: with `items`, without `initialCount` or `lines`.
- `ordered`: List modes only: not with `lines`.

### Load: `ShowMoreLoadProps`

A list the server grows a page at a time: "Show more" asks for the next page and appends it.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `hasMore` (required) | `boolean` |  | Whether there is another page. Without one, the button goes away. |
| `href` | `string` |  | The next page's URL, so the button is a link that works before JavaScript loads. With JavaScript, pressing it calls `onShowMore` instead. |
| `items` (required) | `readonly ReactNode[]` |  | Every item, in order. Each renders in its own list item; give elements a `key`. With `onShowMore`, append each new page here. |
| `loadedLabel` | `((count: number) => string)` | `` (count) => `Showing ${count} items` `` | What screen readers hear after each page, from the number of items now shown. Name the items and translate it here: `` (count) => `Showing ${count} recipes` ``. |
| `loading` | `boolean` |  | Shows the button as busy, and keeps it from asking twice. |
| `loadingLabel` | `string` | `"Loading"` | What screen readers hear when loading starts. Translate it here. |
| `noMoreLabel` | `string` | `"No more items"` | What screen readers hear when a page comes back empty and there is no more. Translate it here. |
| `onShowMore` (required) | `() => void` |  | Asks for the next page. The button is busy while `loading`, and focus moves to the first new item when it arrives. |
| `ordered` | `boolean` | `false` (`<ul>`) | Renders an `<ol>`, for a ranked or numbered list. |

Not in this mode (typed `never`):

- `children`: Text mode only: a list takes `items`.
- `estimate`: Text and height modes only: a list shows its first items.
- `initialCount`: Only with `items` and no `onShowMore`.
- `lines`: 
- `maxHeight`: Height mode only: not with `items`.
- `onOpenChange`: Not for a list that loads more, which only grows.
- `open`: Not for a list that loads more, which only grows.
- `showLessLabel`: A list that loads more never shrinks, so it has no "Show less".

## 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.

- `limit`: Not a prop. Ouro UI uses `initialCount`: `<ShowMore items={items} initialCount={5}>`.
- `maxLines`: Not a prop. Ouro UI uses `lines`: `<ShowMore lines={3}>`.

## `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: ShowMore 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

### `ShowMoreEstimate`

What folded content holds. Not read: ShowMore decides from the browser's layout, and this leaves the first frame as it is. Kept so an app that passes it still compiles.

```ts
interface ShowMoreEstimate {
  /** Its plain text. Not read. */
  text?: string | undefined;
  /** The CSS pixels its pictures and cards take. Not read. */
  height?: number | undefined;
}
```

[^layout-classes]: test:registry/ouro/components/ouro/collapsible/show-more.test.tsx::ShowMore with items > lets app layout classes win and keys rows by their own keys verify:testExists(registry/ouro/components/ouro/collapsible/show-more.test.tsx::ShowMore with items > lets app layout classes win and keys rows by their own keys)
