react-responsive-overflow-list
Version:
A responsive React component that shows as many items as can fit within constraints, hiding overflow items behind a configurable overflow renderer
179 lines (125 loc) • 8.06 kB
Markdown
# react-responsive-overflow-list
Responsive list for React that shows only items that fit and groups the rest into a customizable overflow element. Recalculates on resize.
[](https://www.npmjs.com/package/react-responsive-overflow-list)
[](https://www.npmjs.com/package/react-responsive-overflow-list)
[](https://bundlephobia.com/package/react-responsive-overflow-list)
[](./LICENSE)
**đź”— Live demo:** https://eliav2.github.io/react-responsive-overflow-list/
[](https://stackblitz.com/github/Eliav2/react-responsive-overflow-list)

---
## Features
- Accurate & responsive: measures real layout after paint (ResizeObserver), not guessed widths
- Two usage modes: `children` (simple) or `items + renderItem` (structured)
- Customizable overflow element; ships with a lightweight default
- Multi-row support (via `maxRows`)
- Handles uneven widths, including a single ultra-wide item
- TypeScript types; zero runtime deps (React as peer)
- SSR-friendly: measurement runs on the client
- No implicit wrappers around your items. (layout behaves as you expect)
## Install
```bash
npm i react-responsive-overflow-list
```
## Usage
> In real apps, you’ll typically wrap OverflowList in your own component—design tokens, accessible menus, virtualization, or search. See 'Wrap & extend' below and the demo for a full wrapper example.
### Items + `renderItem` (most common)
Minimal usage with an items array and render function.
```tsx
import { OverflowList } from "react-responsive-overflow-list";
const items = ["One", "Two", "Three", "Four", "Five"];
export default function Example() {
return (
<OverflowList
items={items}
renderItem={(item) => <span style={{ padding: 4 }}>{item}</span>}
style={{ gap: 8 }} // root is display:flex; flex-wrap:wrap
maxRows={1}
/>
);
}
```
### Children pattern
Use children instead of `items + renderItem`.
```tsx
<OverflowList style={{ gap: 8 }}>
<button>A</button>
<button>B</button>
<button>C</button>
<button>D</button>
</OverflowList>
```
### Custom overflow element
Provide your own overflow UI (button, menu, details/summary, etc.).
```tsx
<OverflowList
items={items}
renderItem={(item) => <span>{item}</span>}
renderOverflow={(hidden) => <button>+{hidden.length} more</button>}
/>
```
### Polymorphic root
Render using a different HTML element via `as`.
```tsx
<OverflowList as="nav">
<a href="#home">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
</OverflowList>
```
### Performance control
Trade visual smoothness vs peak performance during rapid resize.
```tsx
<OverflowList
items={items}
renderItem={(item) => <span>{item}</span>}
flushImmediately={false} // uses rAF; faster under rapid resize, may flicker briefly
/>
```
See the **Flush Immediately** example in the live demo.
---
## API (most used)
| Prop | Type | Default | Notes |
| ---------------------- | ---------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------- |
| `items` | `T[]` | — | Use with `renderItem`. Omit when using children. |
| `renderItem` | `(item: T, index: number) => ReactNode` | — | How to render each item. |
| `children` | `ReactNode` | — | Alternative to `items + renderItem`. |
| `as` | `React.ElementType` | `"div"` | Polymorphic root element. |
| `maxRows` | `number` | `1` | Visible rows before overflow. |
| `maxVisibleItems` | `number` | `100` | Hard cap on visible items. |
| `renderOverflow` | `(hidden: T[]) => ReactNode` | default chip | Custom overflow UI. |
| `renderOverflowItem` | `(item: T, i: number) => ReactNode` | `renderItem` | For expanded lists/menus. |
| `renderOverflowProps` | `Partial<OverflowElementProps<T>>` | — | Props for default overflow. |
| `flushImmediately` | `boolean` | `true` | `true` (flushSync, no flicker) vs `false` (rAF, faster under resize). |
| `renderItemVisibility` | `(node: ReactNode, meta: RenderItemVisibilityMeta) => ReactNode` | internal | Control visibility of hidden items (defaults to `React.Activity` if available, otherwise simply return null). |
**Styles:** Root uses `display:flex; flex-wrap:wrap; align-items:center;`. Override via `style`/`className`.
**Default overflow element:** A tiny chip that renders `+{count} more`. Replace via `renderOverflow`.
---
## Wrap & extend
It’s **expected** you’ll wrap `OverflowList` for product needs (design system styling, a11y menus, virtualization, search). for example:
- **Radix UI + Virtualization wrapper** (search, large datasets, a11y, perf):
- **Demo:** see [Radix UI + Virtualization](https://eliav2.github.io/react-responsive-overflow-list/#radix-ui-virtualization-example) in the live site
- [**Source**](demo/src/components/RadixVirtualizedOverflowList.tsx)
- Uses `@tanstack/react-virtual` and the helper `createLimitedRangeExtractor(...)`.
---
## How it works
1. Measure all items and compute how many fit within `maxRows`.
2. Re-test with the overflow indicator; if it would create a new row, hide one more item.
3. Render the stable “normal” state until container size changes.
`flushImmediately=true` → immediate, flicker-free (uses `flushSync`).
`flushImmediately=false` → defer with rAF; smoother under rapid resize, may flicker.
### Edge cases handled
- Single wide item exceeding container width
- `maxRows` / `maxVisibleItems` respected
- Varying item widths, responsive content
- Multi-row overflow detection
### Hidden items & React versions
- In React 19.2+, hidden items use `React.Activity` so overflowed children stay mounted while toggling visibility.
- In React 16–18, overflowed nodes unmount during measurement; pass `renderItemVisibility` if you need to keep custom
elements or skeletons mounted and control visibility yourself.
---
## Requirements
- React ≥ 16.8 (hooks)
- Modern browsers with `ResizeObserver`
## License
MIT