overflow-toolbar
v0.2.4
Published
Responsive toolbar overflow component — items automatically collapse into a dropdown menu as the container shrinks. Supports visible, icon-only (min), and hidden states. Ships with React (Radix UI), MUI, and vanilla JS implementations.
Maintainers
Keywords
Readme
Overflow
A responsive toolbar overflow component that automatically collapses items into a dropdown menu as the container shrinks. Items transition through three states: visible → min (icon-only) → hidden (moved to menu).
Features
- Automatic overflow detection — items collapse into a menu as the container narrows using ResizeObserver
- Three item states — visible, min (icon-only), and hidden (in menu)
- Compact mode — items collapse one at a time instead of all at once
- Reverse mode — collapse from the right instead of the left
- Menu-only items — items that always live in the dropdown (e.g. Help, About)
- Min state — items shrink to icon-only before being fully hidden
- Three implementations — React/Radix UI (shadcn-compatible), Material UI, and vanilla JavaScript
- Tree-shakeable — import only the variant you need via subpath exports
- TypeScript — full type declarations included
Install
npm install overflow-toolbaror
pnpm add overflow-toolbaror
yarn add overflow-toolbarQuick Start
Radix UI / shadcn (React)
import { RxOverflow, RxOverflowItem, RxOverflowMenu } from 'overflow-toolbar/rx';
<RxOverflow style={{ gap: 8 }}>
<RxOverflowMenu opener={<button>More</button>}>
<RxOverflowItem menuId="format"><button>Format</button></RxOverflowItem>
<RxOverflowItem menuId="filter"><button>Filters</button></RxOverflowItem>
</RxOverflowMenu>
<RxOverflowItem menuId="format"><button>Format</button></RxOverflowItem>
<RxOverflowItem menuId="filter"><button>Filters</button></RxOverflowItem>
</RxOverflow>Material UI (React)
import { MuiOverflow, MuiOverflowItem, MuiOverflowMenu } from 'overflow-toolbar/mui';
import { Button, MenuItem } from '@mui/material';
<MuiOverflow sx={{ gap: 1 }}>
<MuiOverflowMenu opener={<Button>More</Button>}>
<MuiOverflowItem menuId="format"><MenuItem>Format</MenuItem></MuiOverflowItem>
<MuiOverflowItem menuId="filter"><MenuItem>Filters</MenuItem></MuiOverflowItem>
</MuiOverflowMenu>
<MuiOverflowItem menuId="format"><Button>Format</Button></MuiOverflowItem>
<MuiOverflowItem menuId="filter"><Button>Filters</Button></MuiOverflowItem>
</MuiOverflow>Vanilla JavaScript
import { OverflowToolbar } from 'overflow-toolbar/vanilla';
const ul = document.querySelector('#my-toolbar');
const toolbar = new OverflowToolbar(ul);
// Later: toolbar.update() after DOM changes
// Cleanup: toolbar.destroy()<ul id="my-toolbar">
<li data-overflow-role="menu">
<button data-menu-trigger>More</button>
<div data-menu-panel>
<button data-menu-id="format">Format</button>
<button data-menu-id="filter">Filters</button>
</div>
</li>
<li data-overflow-role="item" data-menu-id="format">
<button>Format</button>
</li>
<li data-overflow-role="item" data-menu-id="filter">
<button>Filters</button>
</li>
</ul>Subpath Imports
Import only what you need for optimal tree-shaking:
| Import path | Contents |
|---|---|
| overflow-toolbar | Everything (all variants) |
| overflow-toolbar/core | Core React components (Overflow, OverflowItem, OverflowMenu, OverflowController) |
| overflow-toolbar/rx | Radix UI / shadcn variant (RxOverflow, RxOverflowItem, RxOverflowMenu) |
| overflow-toolbar/mui | Material UI variant (MuiOverflow, MuiOverflowItem, MuiOverflowMenu) |
| overflow-toolbar/vanilla | Vanilla JS (OverflowToolbar) |
CSS is included automatically when you import any subpath — no separate style imports needed.
API
Overflow / RxOverflow / MuiOverflow
The container component. Wraps toolbar items and the overflow menu.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Overflow items and menu |
| compact | boolean | false | Collapse items one at a time |
| reverse | boolean | false | Collapse from the right |
| className | string | — | CSS class name |
| style | CSSProperties | — | Inline styles |
| sx | SxProps | — | MUI system props (MUI only) |
OverflowItem / RxOverflowItem / MuiOverflowItem
Wraps each toolbar item. Place matching items in both the toolbar and the menu, linked by menuId.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Item content |
| menuId | string | — | Links toolbar item to its menu counterpart |
| minStateWidth | string | number | — | Width in min state (string for Rx, number for MUI spacing units) |
OverflowMenu / RxOverflowMenu / MuiOverflowMenu
The dropdown menu container. Must include an opener element (the trigger button).
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Menu items |
| opener | ReactNode | — | The button that opens the menu |
| open | boolean | — | Controlled open state. When provided, you manage open/close. |
| onOpenChange | (open: boolean) => void | — | Called when the menu opens or closes. Use alone for notifications, or with open for full control. |
Controlled Menu
By default, the menu manages its own open/close state. To control it programmatically, pass open and onOpenChange:
const [menuOpen, setMenuOpen] = useState(false);
<RxOverflowMenu
opener={<button>More</button>}
open={menuOpen}
onOpenChange={setMenuOpen}
>
{/* items */}
</RxOverflowMenu>
// Close programmatically from anywhere:
setMenuOpen(false);OverflowController
Framework-agnostic controller class for building custom implementations.
| Method | Description |
|---|---|
| connect() | Start observing and apply initial state |
| disconnect() | Stop observing and clear all applied styles/attributes |
| update() | Re-scan children and restart (call after DOM changes) |
Important: No Wrapper Elements
OverflowItem and OverflowMenu components must be direct children of the Overflow container. Do not wrap them in <div>, <span>, layout components, or any other intermediate elements.
The overflow engine scans immediate children for internal role markers to measure, track, and transition items between states. Wrapper elements break this scan, causing items to never collapse, never appear in the menu, or fail to transition between visible/min/hidden states.
{/* WRONG — wrapper breaks overflow behavior */}
<RxOverflow>
<div className="group">
<RxOverflowItem menuId="a"><button>A</button></RxOverflowItem>
<RxOverflowItem menuId="b"><button>B</button></RxOverflowItem>
</div>
</RxOverflow>
{/* CORRECT — items are direct children */}
<RxOverflow>
<RxOverflowItem menuId="a"><button>A</button></RxOverflowItem>
<RxOverflowItem menuId="b"><button>B</button></RxOverflowItem>
</RxOverflow>The same rule applies to the vanilla JS variant — <li> elements with data-overflow-role must be direct children of the <ul> container.
Modes
Compact Mode
Items collapse one at a time with tight spacing. Adjacent buttons get squared-off corners for a grouped look:
<RxOverflow compact>
{/* items... */}
</RxOverflow>Reverse Mode
Items collapse from the right side first instead of the left:
<RxOverflow reverse>
{/* items... */}
</RxOverflow>Min State
Items shrink to a fixed width (icon-only) before being fully hidden:
<RxOverflowItem menuId="format" minStateWidth="2.25rem">
<button><FormatIcon /> Format</button>
</RxOverflowItem>For MUI, minStateWidth accepts a number (theme spacing units):
<MuiOverflowItem menuId="format" minStateWidth={5}>
<Button><FormatIcon /> Format</Button>
</MuiOverflowItem>Menu-Only Items
Items without a menuId always stay where they are — in the toolbar or in the menu:
<RxOverflowMenu opener={<button>More</button>}>
<RxOverflowItem menuId="format"><button>Format</button></RxOverflowItem>
{/* Always in the menu */}
<RxOverflowItem><div role="separator" /></RxOverflowItem>
<RxOverflowItem><button>Help</button></RxOverflowItem>
</RxOverflowMenu>Vanilla JS
The vanilla implementation uses data- attributes for configuration:
| Attribute | Element | Description |
|---|---|---|
| data-overflow-role="item" | <li> | Marks a toolbar item |
| data-overflow-role="menu" | <li> | Marks the menu container |
| data-menu-id | <li>, menu item | Links toolbar and menu items |
| data-min-state-width | <li> | Min-state width (CSS value) |
| data-menu-trigger | <button> | The menu open/close button |
| data-menu-panel | <div> | The menu panel (uses Popover API) |
const toolbar = new OverflowToolbar(document.querySelector('ul'), {
compact: true,
reverse: false,
});
toolbar.update(); // after DOM changes
toolbar.destroy(); // cleanupBrowser Support
Requires ResizeObserver (all modern browsers). The vanilla JS variant also uses the Popover API for the dropdown menu.
Contributing
git clone https://github.com/wesjones/overflow.git
cd overflow
pnpm install
pnpm storybook # dev with Storybook on port 6006
pnpm test:unit # run unit tests
pnpm build:lib # build the library
pnpm typecheck # check types
pnpm lint # lintAlso Known As
This component implements what is commonly known as a responsive toolbar, overflow menu, adaptive toolbar, collapsible toolbar, priority+ pattern, priority-plus navigation, toolbar button group overflow, responsive action bar, action bar, action buttons, overflow actions, button rollup, button rollbar, command bar, or "more menu." It handles responsive buttons, auto-collapse, icon-only collapse, and dynamic toolbar resizing using ResizeObserver. The Radix UI variant is fully compatible with shadcn/ui projects.
