Skip to content

Eliav2/react-responsive-overflow-list

Repository files navigation

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.

npm downloads bundle size license

🔗 Live demo: https://eliav2.github.io/react-responsive-overflow-list/

Open in StackBlitz

Screen Recording 2025-09-27 at 15 49 03


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

npm i react-responsive-overflow-list

shadcn/ui

Install as a styled shadcn component with dropdown menu overflow:

# Radix UI primitives (recommended)
npx shadcn@latest add https://eliav2.github.io/react-responsive-overflow-list/r/styles/radix-vega/overflow-list.json

# Base UI primitives
npx shadcn@latest add https://eliav2.github.io/react-responsive-overflow-list/r/styles/base-vega/overflow-list.json

See shadcn/ui docs for usage.

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.

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.

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

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

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

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


Headless hook (useOverflowList)

Same measurement engine, bring your own layout. Use this when the <OverflowList /> render shape doesn't fit — e.g. a filter chip bar with custom slots, a layout that interleaves overflow logic with other elements, or any case where you want full control of the JSX.

Attach containerRef to your wrap container and overflowIndicatorRef to your +N element. Render all items during the "measuring" phase so widths are observable; unmount overflowed items (or use display:none / React.Activity) in the "normal" phase — they must not consume layout space, or the overflow indicator can't fit on the last row.

import { useOverflowList } from "react-responsive-overflow-list";

const items = ["One", "Two", "Three", "Four", "Five", "Six"];

export function CustomChipBar() {
  const { containerRef, overflowIndicatorRef, visibleCount, hiddenCount, phase, showOverflow } =
    useOverflowList<HTMLDivElement, HTMLSpanElement>({ itemCount: items.length, maxRows: 1 });

  return (
    <div ref={containerRef} style={{ display: "flex", flexWrap: "wrap", gap: 8, minWidth: 0 }}>
      {items.map((item, index) => {
        const visible = phase === "measuring" || index < visibleCount;
        if (!visible) return null;
        return <span key={index} style={{ padding: 4 }}>{item}</span>;
      })}
      {showOverflow && <span ref={overflowIndicatorRef}>+{hiddenCount} more</span>}
    </div>
  );
}

Hook options

Option Type Default Notes
itemCount number Total items the consumer will render.
maxRows number 1 Visible rows before overflow.
maxVisibleItems number 100 Hard cap on visible items.
flushImmediately boolean true true (flushSync, no flicker) vs false (rAF, smoother).

Hook return

Property Type Notes
containerRef React.RefObject<T | null> Attach to the flex-wrap container.
overflowIndicatorRef React.RefObject<T | null> Attach to the +N indicator element so its width is measured.
visibleCount number Final count of items that fit (already accounts for the indicator's reserved row).
hiddenCount number itemCount - visibleCount.
phase "normal" | "measuring" | "measuring-overflow-indicator" Gate item visibility on this — render all during "measuring".
showOverflow boolean true when the overflow indicator should be rendered.

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):


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

About

Responsive list for React that shows only items that fit and groups the rest into a customizable overflow element

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors