OverflowRegion
Hide trailing items instead of wrapping when space runs out.
The <OverflowRegion> component keeps a row of elements on a single line. When there is not enough horizontal space, it hides its trailing items instead of wrapping them to a new line, and restores them as soon as space returns. Hidden items stay mounted, so they keep their state, but they are removed from painting, the tab order, and the accessibility tree.
The region measures its own container, not the viewport. A row inside a narrow panel on a wide screen collapses correctly, which viewport-based media queries cannot do. Every demo on this page sits in a resizable frame. Drag the handle on the right (or focus it and use the arrow keys) to see the row react.
Usage
Place the items as direct children, ordered by priority. The last child is hidden first when space runs out. Use the indicator render prop to show an element while items are hidden, such as a counter or a menu. The indicator only appears while at least one item is hidden.
Items are spaced by the space prop. When the region sits inside a spacing-aware layout component such as <Inline>, it inherits that component's spacing automatically, so a nested row lines up with its surroundings without extra configuration. Set space explicitly to override, or when the region has no such parent.
A typical use case is a toolbar: instead of wrapping to a second line and pushing the layout around, actions that no longer fit collapse into a "More" menu. Because priority is DOM order, actions.slice(visibleCount) is exactly the hidden set.
import { Button, Menu, OverflowRegion } from '@marigold/components';import { Archive, Copy, Pencil, Share2, Trash2 } from '@marigold/icons';import { DemoResizer } from '@/ui/DemoResizer';const actions = [ { id: 'edit', label: 'Edit', icon: <Pencil /> }, { id: 'duplicate', label: 'Duplicate', icon: <Copy /> }, { id: 'share', label: 'Share', icon: <Share2 /> }, { id: 'archive', label: 'Archive', icon: <Archive /> }, { id: 'delete', label: 'Delete', icon: <Trash2 /> },];// A toolbar that collapses instead of wrapping: actions that no longer// fit move into the "More" menu. Priority is DOM order, so// `actions.slice(visibleCount)` is exactly the hidden set.export default () => ( <DemoResizer defaultWidth={440} minWidth={220}> <OverflowRegion indicator={({ visibleCount }) => ( <Menu label="More" aria-label="More actions"> {actions.slice(visibleCount).map(action => ( <Menu.Item key={action.id} id={action.id}> {action.icon} {action.label} </Menu.Item> ))} </Menu> )} > {actions.map(action => ( <Button key={action.id} variant="ghost"> {action.icon} {action.label} </Button> ))} </OverflowRegion> </DemoResizer>);Hiding is not a recovery strategy
Every hidden item must stay reachable through another surface. Always pair the
region with an indicator that opens the hidden items, or with an external
surface that always contains them. Never use the region
to silently drop content.
Priority+ navigation
The same pattern applied to navigation is known as Priority+: the most important links stay visible and the rest move into a "More" menu. Order the links by importance and feed the hidden set to the menu so every page stays reachable.
import { Link, Menu, OverflowRegion } from '@marigold/components';import { DemoResizer } from '@/ui/DemoResizer';const links = ['Dashboard', 'Events', 'Orders', 'Reports', 'Settings', 'Team'];// Priority+ navigation: the region hides trailing links as space runs out.// Since priority is DOM order, `links.slice(visibleCount)` is exactly the// hidden set — feed it to the "More" menu so every page stays reachable.export default () => ( <DemoResizer defaultWidth={400}> <nav aria-label="Main"> <OverflowRegion indicator={({ visibleCount }) => ( <Menu label="More" aria-label="More pages"> {links.slice(visibleCount).map(link => ( <Menu.Item key={link} id={link}> {link} </Menu.Item> ))} </Menu> )} > {links.map(link => ( <Link key={link} href="#"> {link} </Link> ))} </OverflowRegion> </nav> </DemoResizer>);External surfaces
When the surface that holds the hidden items already exists outside the region, you do not need an indicator at all. A common example is a filter bar: the quick filters demote as space runs out, while a pinned "All filters" panel always contains the complete filter set. The pinned trigger itself is the recovery surface.
Note that nothing is transferred into the panel. Because it always renders every filter, a demoted quick filter is simply also available there, in a stable place that does not depend on the current width. If you want a surface that shows only the hidden items, use the indicator render prop with a menu instead, as in the toolbar example above.
If the trigger carries a counter badge, count applied filters, never hidden ones. A number on a filter button conventionally reads as "n filters are active", so a count that changes while the user resizes the window misleads them about their data. Applied filters also cover the risky case: a filter that is active but currently demoted stays represented in the count.
Pinned elements are regular siblings of the region inside a non-wrapping row (such as <Inline noWrap>). They keep their size while the region flexes and clips.
import { useState } from 'react';import { Badge, Button, Checkbox, Drawer, Inline, NumberField, OverflowRegion, SearchField, Select, Stack,} from '@marigold/components';import { DemoResizer } from '@/ui/DemoResizer';// The badge counts *applied* filters, following the common convention for// filter buttons. It is not tied to the overflow state: hiding a quick// filter changes nothing about what is applied, and the panel is always// complete, so demoted filters need no extra signal.export default () => { const [applied, setApplied] = useState({ category: false, status: false, price: false, }); const appliedCount = Object.values(applied).filter(Boolean).length; const apply = (filter: keyof typeof applied) => () => setApplied(current => ({ ...current, [filter]: true })); return ( <DemoResizer defaultWidth={620} minWidth={420}> <Inline noWrap space="related" alignY="center"> <SearchField aria-label="Search events" width={44} /> <OverflowRegion> <Select aria-label="Category" placeholder="Category" width={36} onChange={apply('category')} > <Select.Option id="concerts">Concerts</Select.Option> <Select.Option id="festivals">Festivals</Select.Option> <Select.Option id="theater">Theater</Select.Option> </Select> <Select aria-label="Status" placeholder="Status" width={36} onChange={apply('status')} > <Select.Option id="published">Published</Select.Option> <Select.Option id="draft">Draft</Select.Option> </Select> <Select aria-label="Price" placeholder="Price" width={36} onChange={apply('price')} > <Select.Option id="lt50">Under 50 €</Select.Option> <Select.Option id="gte50">50 € and more</Select.Option> </Select> </OverflowRegion> <Drawer.Trigger> <Button> All filters {appliedCount > 0 && ( <Badge variant="primary">{appliedCount}</Badge> )} </Button> <Drawer closeButton size="xsmall"> <Drawer.Title>All filters</Drawer.Title> <Drawer.Content> {/* The panel always renders the complete filter set. Nothing moves when quick filters demote, so every filter has a stable place in here. */} <Stack space="group"> <Checkbox.Group label="Category"> <Checkbox value="concerts" label="Concerts" /> <Checkbox value="festivals" label="Festivals" /> <Checkbox value="theater" label="Theater" /> </Checkbox.Group> <Checkbox.Group label="Status"> <Checkbox value="published" label="Published" /> <Checkbox value="draft" label="Draft" /> </Checkbox.Group> <NumberField label="Max. price" minValue={0} step={10} /> </Stack> </Drawer.Content> </Drawer> </Drawer.Trigger> </Inline> </DemoResizer> );};For cases where you do need to react to demotions, for example to track them in analytics, onOverflowChange reports the region's state whenever the visible count changes.
Items must be direct children
The region measures and hides each direct child individually. A single component that renders several elements counts as one item, so spread the items directly into the region instead of grouping them in a wrapper component or fragment.
Accessibility
Hidden items are removed from painting, the tab order, and the accessibility tree (via inert and aria-hidden), so keyboard and screen reader users never land on an item they cannot see. Since the items stay mounted, entered values and open states survive being hidden and restored.
The indicator is part of the row's natural tab order and only rendered while items are hidden. Make sure it has an accessible name that explains what it reveals, such as "More actions".
Props
OverflowRegion
Prop
Type
Accessibility props (6)
Prop
Type
Alternative components
Inline: If wrapping to a new line is acceptable, use the default
<Inline>behavior instead. It is simpler and needs no recovery surface.TagGroup: For rows of tags, use
<TagGroup>with itscollapseAtprop. It caps the visible tags at a fixed count behind a "Show more" toggle and keeps the tags interactive. Compose a region with a+Ncounter only for a read-only preview in a dense spot, such as a table cell.Breadcrumbs: Collapses from the middle (keeping the first and current page) with a built-in ellipsis menu. Use it for paths instead of composing a region.