Marigold
v18.0.0-rc.5
Marigold
v18.0.0-rc.5

Application

MarigoldProvider
RouterProvider

Layout

AppShellbeta
Aside
Aspect
Center
Columns
Container
Grid
Inline
Inset
OverflowRegionbeta
Pagebeta
Panelbeta
Scrollable
Split
Stack
Tiles

Actions

Buttonupdated
ButtonGroupbeta
Link
LinkButton
ToggleButtonbeta

Form

Autocomplete
Calendar
Checkbox
ComboBox
DateField
DatePicker
DateRangePickerbeta
FileField
Form
NumberField
Radio
RangeCalendaralpha
SearchField
SegmentedControlbeta
Select
SelectListupdated
Slider
Switchupdated
TagFieldbeta
TextArea
TextField
TimeField

Collection

Cardupdated
Table
Tag
ActionBaralpha

Navigation

Accordion
Breadcrumbs
Pagination
Sidebarbeta
Tabs
TopNavigationbeta

Overlay

ActionMenualpha
ContextualHelp
Dialog
Drawer
Menuupdated
Toastbeta
Tooltip

Content

Badge
Descriptionalpha
Divider
EmptyStatebeta
ErrorStatebeta
Headline
Keyboardbeta
List
Loader
SectionMessage
SVG
Text
TextValuealpha
Titlealpha

Formatters

DateFormat
NumericFormat

Hooks and Utils

cn
cva
extendTheme
parseFormData
useAsyncListData
useLandmark
useListData
useTheme
VisuallyHidden
Components

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.

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

Dashboard
Events
Orders
Reports
Settings
Team
400px
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.

620px
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

Did you know? You can explore, test, and customize props live in Marigold's storybook. Watch the effects they have in real-time!
View OverflowRegion stories

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 its collapseAt prop. It caps the visible tags at a fixed count behind a "Show more" toggle and keeps the tags interactive. Compose a region with a +N counter 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.

Related

Building layouts

Learn how to build layouts.

Inline

Display children horizontally in a row.
Last update: 4 minutes ago

Inset

Adds space around its children.

Page

The main content area of a screen, with its title, description, primary action, and the sections beneath them.

On this page

UsagePriority+ navigationExternal surfacesItems must be direct childrenAccessibilityPropsOverflowRegionAlternative componentsRelated