---
title: 'SidebarMenu'
description: 'Use SidebarMenu for persistent navigation with sections and nested groups.'
version: 11.14.0
generatedAt: 2026-09-16T12:30:59.114Z
checksum: 3549bdd9a49af553458b375c5d5c07a76311a0de977d907a629d006366567bd4
---

# SidebarMenu

## Import

```tsx
import * as SidebarMenu from '@dnb/eufemia/extensions/sidebar-menu'
import '@dnb/eufemia/extensions/sidebar-menu/style'
```

## Description

`SidebarMenu` provides persistent navigation with expandable groups and optional top-level sections. Its compound API is tree-shakeable: `SidebarMenu` itself is a namespace, while only the subcomponents you use are included.

Compose a menu declaratively with `SidebarMenu.Root`, `SidebarMenu.Item`, `SidebarMenu.Accordion`, `SidebarMenu.Group`, `SidebarMenu.Section`, `SidebarMenu.Header`, and `SidebarMenu.Divider`. Use `SidebarMenu.Data` when the menu is generated from recursive `data` or `sections` objects. The declarative API keeps unused parts outside the bundle, while `Data` includes all parts needed to render arbitrary object trees.

Fragments and memoized SidebarMenu parts are supported. Arbitrary wrapper components are opaque to Root's selected-route discovery; pass generated structures through `SidebarMenu.Data` instead.

## Design goals

- Work with SSR and SSG. Stable prerendered markup, storage metadata, and CSS-driven responsive visibility avoid layout shifts during hydration.
- Provide accessible navigation defaults through landmarks, semantic lists, native links and buttons, keyboard support, focus restoration, and reduced-motion handling.
- Stay modular and tree-shakeable through compound exports, so applications include only the parts they use.
- Support both declarative composition and recursive `data` or `sections` objects with the same behavior.
- Allow controlled, uncontrolled, and persisted state for routing, accordions, sections, selection, and scroll position.
- Adapt to different application structures through nested groups, custom router links, responsive inline and Drawer layouts, resizing, badges, and custom content.
- Allow resizable desktop navigation to be temporarily dismissed and restored without changing the small-screen Drawer behavior.
- Preserve browser-native behavior, including find-in-page through collapsed content.

## Key behaviors

- Setting `selectedItem` reveals its ancestors and section. An active branch can still be collapsed; its label then uses medium font weight to indicate that it contains the current page.
- An off-screen selected item is brought into view. Initial positioning and restored scroll positions happen without a visible animated jump.
- `openItemsStorageKey` and `scrollPositionStorageKey` preserve navigation context between route changes. Session storage is used by default.
- A linked accordion uses one action for navigation and expansion.
- Collapsed content remains available to browser find-in-page through `hidden="until-found"`.
- A notification badge on a nested item is summarized by a red dot on collapsible ancestor accordions. The dot remains visible when the accordion expands and takes priority over an informational badge on the same menu point. An explicit notification badge remains visible instead of the dot. Automatic detection follows direct SidebarMenu children and cannot inspect items hidden inside another React component.
- Sections do not automatically summarize nested notifications. Set `badge` for the dropdown option and `triggerBadge` for the selected trigger when a section needs a notification count.
- Section hover previews dim the current content only on devices with a fine pointer and hover support. Touch selection happens on the first tap without dimming.

## Composition

Accordion groups support deep nesting, but keep the hierarchy as shallow as possible. Use `SidebarMenu.Group` for a titled category whose items should always remain visible, or set `collapsible={false}` on an accordion. Groups can optionally navigate using `href` or `to`; in the data API, use `type: 'group'`.

Menu point rows have a minimum height of 44px. Top-level rows have 16px between them, while nested rows have 8px. The first accordion level is indented by 48px and each deeper accordion level adds 16px.

Set `dividerBefore: true` on a data item to render a separator, or place `SidebarMenu.Divider` between declarative items. Set `openOnFind={false}` on Root or Data when collapsed content should not use `hidden="until-found"`.

When an accordion also represents a page, provide `href` or `to`. The accordion becomes a navigation link that also controls expansion.

## State and scrolling

Use controlled properties when application routing or external state owns the menu:

- `selectedItem` and `onSelectedItemChange` for the current destination.
- `openItems` and `onOpenItemsChange` for expanded groups.
- `activeSection` and `onActiveSectionChange` for the visible section.

The selected destination is positioned within the visible browser or nearest `ScrollView`. Wrap the menu in `ScrollView` and set a fixed or viewport-based `maxHeight` when it needs its own scrolling region.

Use `openItemsStorageKey` to preserve manually opened and closed groups. When the menu is inside a `ScrollView`, use `scrollPositionStorageKey` to restore its vertical position. A stored value takes precedence over the defaults, including an empty array. Use local storage only when the state should survive browser restarts; otherwise keep the default session storage.

### SSR and SSG without layout shift

`Root` and `Data` render storage metadata when `openItemsStorageKey` or `scrollPositionStorageKey` is set. Add `PreHydrationScript` after the prerendered menu markup and before the application hydration script to make persisted state visible on the first paint:

```tsx
import * as SidebarMenu from '@dnb/eufemia/extensions/sidebar-menu'

function Document({ children, nonce }) {
  return (
    <html>
      <body>
        <div id="app">{children}</div>
        <SidebarMenu.PreHydrationScript nonce={nonce} />
        <script src="/client.js" nonce={nonce} />
      </body>
    </html>
  )
}
```

The script reads the configured session or local storage, applies the persisted open and closed accordion state, and restores the nearest `ScrollView`. An exact stored scroll position, including the top position, takes priority over initially centering the selected item. Without a stored position, the selected item is kept visible. `Root` removes the temporary CSS during its layout effect after React has applied the same state. The script and generated style inherit the supplied CSP nonce.
Render the script once per document; it handles every SidebarMenu with persistence metadata. Controlled `openItems` does not use storage and therefore does not emit open-state metadata.

Use `getPreHydrationScript()` when the HTML template is assembled outside React. Insert its return value in a blocking script at the same location: after the menu markup and before the hydration bundle.

A layout effect alone cannot prevent this shift in an SSG page. The browser can paint the static HTML before it downloads the application bundle and starts hydration. The blocking script runs while the HTML is parsed, so the first paint already matches storage. It is only needed when persisted state must affect the prerendered result.

## Resizable layouts

Use `SidebarMenu.ResizeHandle` when people should be able to resize a sidebar. These properties connect the handle to the layout:

- `targetRef` points to the element being resized. Its rendered width is the starting value.
- `cssProperty` receives the new width in pixels. The sidebar CSS must use the same custom property.
- `scopeSelector` selects the closest shared layout ancestor on which the custom property is set. Use it when adjacent content also needs to respond to the sidebar width. Omit it when only the sidebar needs the value.
- `minWidth` and `maxWidth` define the allowed resize range. They default to 240px and 560px and are also constrained by the viewport and the target's CSS `max-width`.

The handle supports pointer dragging, arrow-key resizing, Shift for larger keyboard steps, and Enter or double-click to reset.
When `onCollapse` is provided, dragging below `minWidth` adds resistance to communicate that the sidebar can continue towards dismissal. Releasing returns it to `minWidth`. Reaching `collapseThreshold` calls `onCollapse`; the threshold defaults to half of `minWidth`. Pair it with `useResponsive().collapseInline()` to dismiss desktop inline navigation and let `ResponsiveTrigger` restore it.

The resistance moves the sidebar by 10% of the cursor distance below `minWidth`. For example, dragging 100px further moves the visible edge about 10px. Pointer resizing above the minimum stays immediate. Width easing belongs to the surrounding layout. Enable it after the handle adds `dnb-sidebar-menu-resize-handle--transition-ready`, and disable it while `dnb-sidebar-menu-resize-handle--dragging` is present. Both classes are added to the element matched by `scopeSelector`.

The resize handle is optional and its styles are included with the SidebarMenu extension stylesheet. It does not persist width. The CSS-defined initial width is therefore used after a reload, and no script is needed before React renders.

```tsx
const sidebarRef = useRef<HTMLElement>(null)

<div className="layout">
  <aside
    id="sidebar"
    ref={sidebarRef}
    className="sidebar"
  >
    <SidebarMenu.Root aria-label="Main navigation" />
  </aside>
  <SidebarMenu.ResizeHandle
    targetRef={sidebarRef}
    scopeSelector=".layout"
    cssProperty="--sidebar-width"
    aria-controls="sidebar"
  />
</div>
```

```css
.layout {
  --sidebar-width: 20rem;

  display: grid;
  grid-template-columns: var(--sidebar-width) 1fr;
}

.sidebar {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--sidebar-width);
  overflow: hidden auto;
}
```

## Responsive navigation

Use the responsive compound parts when the same navigation should be inline on larger screens and available through a hamburger-triggered Drawer on smaller screens:

- `ResponsiveProvider` owns the breakpoint and Drawer state.
- `ResponsiveProvider` can also own a collapsible desktop inline state.
- `ResponsiveTrigger` renders a hamburger button below the breakpoint or while the desktop inline navigation is collapsed. On desktop it restores the inline navigation instead of opening a Drawer.
- `ResponsiveInline` renders its children only above the breakpoint.
- `ResponsiveDrawer` renders a left-side Drawer only below the breakpoint. Its close button is placed on the left.
- `useResponsive().close()` closes the Drawer after mobile navigation.

The trigger and Drawer do not need to be siblings. Place both under the same provider, for example with the trigger in an application header and the Drawer beside the desktop navigation.

The default `medium` breakpoint uses the bundled responsive CSS. A custom Eufemia breakpoint name or explicit `em` value adds scoped first-paint CSS, so server-rendered visibility uses the same breakpoint before and after hydration. Pass `styleNonce` when your content security policy requires a nonce for inline styles.

Use `inlineCollapsed` with `onInlineCollapsedChange` when application state controls desktop dismissal, or `defaultInlineCollapsed` for an uncontrolled initial state. `useResponsive()` exposes `inlineCollapsed`, `collapseInline()`, and `restoreInline()` to compound children.

### Dismissible desktop navigation

Combine `ResizeHandle`, the responsive parts, and one shared width property to let people temporarily dismiss a desktop sidebar:

```tsx
function Navigation() {
  const sidebarRef = useRef<HTMLElement>(null)
  const { collapseInline } = SidebarMenu.useResponsive()

  return (
    <>
      <header className="header">
        <span className="header-leading">
          <SidebarMenu.ResponsiveTrigger
            controls="mobile-navigation"
            inlineControls="desktop-navigation"
          />
          <a href="/">Brand</a>
        </span>
      </header>
      <SidebarMenu.ResponsiveInline>
        <aside
          id="desktop-navigation"
          ref={sidebarRef}
          className="sidebar"
        >
          <SidebarMenu.Root aria-label="Main navigation" />
          <SidebarMenu.ResizeHandle
            targetRef={sidebarRef}
            scopeSelector=".layout"
            cssProperty="--sidebar-restored-width"
            onCollapse={collapseInline}
            aria-controls="desktop-navigation"
          />
        </aside>
      </SidebarMenu.ResponsiveInline>
      <SidebarMenu.ResponsiveDrawer id="mobile-navigation">
        <SidebarMenu.Root aria-label="Main navigation" />
      </SidebarMenu.ResponsiveDrawer>
    </>
  )
}

function AppNavigation() {
  const layoutRef = useRef<HTMLDivElement>(null)

  return (
    <div ref={layoutRef} className="layout">
      <SidebarMenu.ResponsiveProvider
        onInlineCollapsedChange={(collapsed) => {
          layoutRef.current?.toggleAttribute(
            'data-sidebar-collapsed',
            collapsed
          )
        }}
      >
        <Navigation />
      </SidebarMenu.ResponsiveProvider>
      <main className="content">Page content</main>
    </div>
  )
}
```

```css
.layout {
  --sidebar-restored-width: 20rem;
  --sidebar-width: var(--sidebar-restored-width);
}

.layout[data-sidebar-collapsed] {
  --sidebar-width: 0px;
}

.sidebar {
  width: var(--sidebar-width);
}

.layout.dnb-sidebar-menu-resize-handle--transition-ready .sidebar {
  transition: width 300ms var(--easing-default);
}

.content {
  margin-inline-start: var(--sidebar-width);
}

.layout.dnb-sidebar-menu-resize-handle--transition-ready .content {
  transition: margin-inline-start 300ms var(--easing-default);
}

.header {
  inset-inline-start: var(--sidebar-width);
  width: calc(100% - var(--sidebar-width));
}

.layout.dnb-sidebar-menu-resize-handle--transition-ready .header {
  transition:
    inset-inline-start 300ms var(--easing-default),
    width 300ms var(--easing-default);
}

.header-leading {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.layout.dnb-sidebar-menu-resize-handle--dragging
  :is(.sidebar, .content, .header) {
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .sidebar,
  .content,
  .header {
    transition: none;
  }
}
```

The desktop trigger controls the hidden inline navigation and restores it at the CSS default width, like Enter or double-click on `ResizeHandle`, after which the handle can resize it again. This recipe fixes the sidebar to the viewport, clips horizontal overflow while preserving vertical scrolling, and reserves the same width beside it for page content. The sidebar must own clipping because `ResponsiveInline` uses `display: contents` to preserve the consumer's layout; `inert` and `aria-hidden` prevent interaction but do not hide painted overflow. Keep the trigger and brand together at the start of the header so they replace the dismissed sidebar's leading content. Below the responsive breakpoint, the same trigger controls the Drawer instead. A desktop-dismissed state can remain while crossing the breakpoint; returning to desktop shows the restore trigger again. The inline tree stays mounted but inert and `aria-hidden` while dismissed so the width can animate without leaving hidden navigation interactive.

## Relevant links

- [Source code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-eufemia/src/extensions/sidebar-menu)
- [Docs code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-design-system-portal/src/docs/uilib/extensions/sidebar-menu)

## Accessibility

- Root and Data render a `nav` landmark. Give each menu a distinct `aria-label` or `aria-labelledby` value.
- Navigation levels use semantic nested lists.
- Items render as native links when `href` or `to` is supplied, and native buttons otherwise.
- Accordion triggers use `aria-expanded` and `aria-controls`. They are native buttons unless `href` or `to` makes the trigger a link.
- Selected destinations use `aria-current="page"`.
- Disabled button items use the native `disabled` attribute. Disabled links use `aria-disabled`, have no destination, and are removed from the tab order.
- Sections are selected through a labeled dropdown.
- **Tab/Shift+Tab** moves through interactive elements. **Enter/Space** activates buttons and accordion triggers.
- Expansion and dismiss/restore animations respect `prefers-reduced-motion`.

The component has automated axe coverage. Always test the finished navigation with the screen readers supported by your product because labels and hierarchy depend on application content.

<RelatedComponents />


## Demos

### Declarative composition

Use compound React components when the menu structure is authored in JSX.
This example persists manually opened and closed groups in session storage.


```tsx
render(<SidebarMenu.Root aria-label="Personal and business navigation" defaultActiveSection="personal" defaultOpenItems={['products']} openItemsStorageKey="sidebar-menu-declarative-example" onOpenItemsChange={openItems => {
  console.log('openItems', openItems);
}} onSelectedItemChange={itemId => {
  console.log('selectedItem', itemId);
}}>
        <SidebarMenu.Section id="personal" text="Personal" icon={person} badge={2} badgeProps={{
    variant: 'notification',
    label: 'Notifications:'
  }} triggerBadge={2} triggerBadgeProps={{
    label: 'Notifications:'
  }}>
          <SidebarMenu.Header text="Everyday banking" />
          <SidebarMenu.Item id="overview" text="Overview" icon={home} badge="New" badgeProps={{
      status: 'positive',
      subtle: true
    }} />
          <SidebarMenu.Accordion id="products" text="Products" badge={3} badgeProps={{
      label: 'Product groups:'
    }}>
            <SidebarMenu.Item id="accounts" text="Accounts" icon={account} />
            <SidebarMenu.Accordion id="cards" text="Cards" icon={card}>
              <SidebarMenu.Item id="debit-card" text="Debit card" />
              <SidebarMenu.Item id="credit-card" text="Credit card" />
              <SidebarMenu.Accordion id="card-settings" text="Settings">
                <SidebarMenu.Item id="limits" text="Limits" />
                <SidebarMenu.Accordion id="security" text="Security">
                  <SidebarMenu.Item id="biometrics" text="Biometrics" />
                  <SidebarMenu.Item id="pin-code" text="PIN code" />
                </SidebarMenu.Accordion>
              </SidebarMenu.Accordion>
            </SidebarMenu.Accordion>
            <SidebarMenu.Accordion id="card-controls" text="Controls" icon={cog}>
              <SidebarMenu.Item id="freeze-card" text="Freeze card" />
              <SidebarMenu.Item id="online-shopping" text="Online shopping" badge={2} badgeProps={{
          variant: 'notification',
          label: 'Notifications:'
        }} />
            </SidebarMenu.Accordion>
          </SidebarMenu.Accordion>
          <SidebarMenu.Divider />
          <SidebarMenu.Item id="disabled" text="Disabled item" disabled />
        </SidebarMenu.Section>

        <SidebarMenu.Section id="business" text="Business" icon={office_buildings} badge={9} badgeProps={{
    label: 'Notifications:'
  }}>
          <SidebarMenu.Header text="Company" />
          <SidebarMenu.Item id="business-overview" text="Overview" icon={home} />
          <SidebarMenu.Item id="business-payments" text="Payments" icon={pay_from} />
          <SidebarMenu.Accordion id="business-services" text="Services">
            <SidebarMenu.Item id="business-accounts" text="Accounts" icon={account} />
            <SidebarMenu.Accordion id="business-cards" text="Company cards" icon={card}>
              <SidebarMenu.Item id="business-card-overview" text="Card overview" />
              <SidebarMenu.Item id="business-card-settings" text="Card settings" />
            </SidebarMenu.Accordion>
            <SidebarMenu.Accordion id="business-financing" text="Financing">
              <SidebarMenu.Item id="business-loans" text="Loans" />
              <SidebarMenu.Item id="business-credit" text="Credit" />
            </SidebarMenu.Accordion>
          </SidebarMenu.Accordion>
        </SidebarMenu.Section>
      </SidebarMenu.Root>)
```


### Data composition

Use `sections` or `data` to render the same recursive structure from JavaScript objects.
This example is placed inside a `ScrollView` with a viewport-aware maximum height and persists its scroll position in session storage.


```tsx
render(<ScrollView interactive="auto" scrollbarGutter="stable" style={{
  maxHeight: 'min(18rem, calc(100vh - 8rem))'
}}>
        <SidebarMenu.Data aria-label="Data-driven navigation" defaultSelectedItem="data-credit-card" scrollSelectedItemIntoView={false} space="small" scrollPositionStorageKey="sidebar-menu-data-scroll-position" sections={[{
    id: 'personal-data',
    text: 'Personal',
    icon: person,
    defaultActive: true,
    triggerBadge: 2,
    triggerBadgeProps: {
      label: 'Notifications:'
    },
    items: [{
      id: 'data-home',
      text: 'Overview',
      icon: home,
      badge: 'New',
      badgeProps: {
        status: 'positive',
        subtle: true
      }
    }, {
      id: 'data-products',
      text: 'Products',
      badge: 3,
      badgeProps: {
        label: 'Product groups:'
      },
      items: [{
        id: 'data-cards',
        text: 'Cards',
        icon: card,
        items: [{
          id: 'data-debit-card',
          text: 'Debit card'
        }, {
          id: 'data-credit-card',
          text: 'Credit card'
        }, {
          id: 'data-settings',
          text: 'Settings',
          items: [{
            id: 'data-security',
            text: 'Security',
            items: [{
              id: 'data-biometrics',
              text: 'Biometrics'
            }, {
              id: 'data-pin-code',
              text: 'PIN code'
            }]
          }]
        }]
      }, {
        id: 'data-controls',
        text: 'Controls',
        icon: cog,
        items: [{
          id: 'data-freeze-card',
          text: 'Freeze card'
        }, {
          id: 'data-online-shopping',
          text: 'Online shopping',
          badge: 2,
          badgeProps: {
            variant: 'notification',
            label: 'Notifications:'
          }
        }]
      }]
    }]
  }, {
    id: 'business-data',
    text: 'Business',
    icon: office_buildings,
    badge: 9,
    badgeProps: {
      label: 'Notifications:'
    },
    items: [{
      id: 'data-business-home',
      text: 'Overview',
      icon: home
    }, {
      id: 'data-payments',
      text: 'Payments',
      icon: pay_from
    }, {
      id: 'data-services',
      text: 'Services',
      items: [{
        id: 'data-accounts',
        text: 'Accounts',
        icon: account
      }, {
        id: 'data-company-cards',
        text: 'Company cards',
        icon: card,
        items: [{
          id: 'data-card-overview',
          text: 'Card overview'
        }, {
          id: 'data-card-settings',
          text: 'Card settings'
        }]
      }, {
        id: 'data-financing',
        text: 'Financing',
        items: [{
          id: 'data-loans',
          text: 'Loans'
        }, {
          id: 'data-credit',
          text: 'Credit'
        }]
      }]
    }]
  }]} />
      </ScrollView>)
```


### Responsive drawer

The same data renders as an inline menu on larger screens and behind a hamburger button on smaller screens. `ResponsiveProvider` owns the breakpoint, Drawer state, and optional collapsed-inline state, while `ResponsiveDrawer` handles the dialog, focus trapping, Escape key, backdrop, left-side close button, and focus return.

Drag the demo viewport to the medium breakpoint or narrower to reveal the hamburger button and try the drawer.
For a resizable desktop sidebar that can be dragged past its minimum width and restored from the same trigger, see [Dismissible desktop navigation](/uilib/extensions/sidebar-menu/#dismissible-desktop-navigation).


```tsx
const ResponsiveMenu = () => {
  const items = [{
    id: 'home',
    text: 'Home',
    href: '#home'
  }, {
    id: 'products',
    text: 'Products',
    items: [{
      id: 'accounts',
      text: 'Accounts',
      href: '#accounts'
    }, {
      id: 'cards',
      text: 'Cards',
      href: '#cards'
    }]
  }];
  const {
    close
  } = SidebarMenu.useResponsive();
  const menuItems = items.map(item => ({
    ...item,
    onClick: item.href ? close : undefined,
    items: item.items?.map(item => ({
      ...item,
      onClick: close
    }))
  }));
  return <>
              <SidebarMenu.ResponsiveTrigger controls="responsive-menu-drawer" text="Menu" />
              <SidebarMenu.ResponsiveInline>
                <SidebarMenu.Data aria-label="Main navigation" data={menuItems} />
              </SidebarMenu.ResponsiveInline>
              <SidebarMenu.ResponsiveDrawer id="responsive-menu-drawer" dialogTitle="Menu">
                <SidebarMenu.Data aria-label="Main navigation" data={menuItems} />
              </SidebarMenu.ResponsiveDrawer>
            </>;
};
render(<SidebarMenu.ResponsiveProvider breakpoint="medium">
            <ResponsiveMenu />
          </SidebarMenu.ResponsiveProvider>);
```

## SidebarMenu.Root


```json
{
  "props": {
    "children": {
      "doc": "Declarative menu content composed with SidebarMenu.Item, SidebarMenu.Accordion, SidebarMenu.Group, SidebarMenu.Section, SidebarMenu.Header, and SidebarMenu.Divider.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "openItems": {
      "doc": "Controlled array of open accordion ids. Selected ancestors are only opened automatically when this property is not provided.",
      "type": "Array<string>",
      "status": "optional"
    },
    "defaultOpenItems": {
      "doc": "Accordion ids that are initially open.",
      "type": "Array<string>",
      "defaultValue": "[]",
      "status": "optional"
    },
    "openItemsStorageKey": {
      "doc": "Storage key used to persist the user's open accordion state between route navigations.",
      "type": "string",
      "status": "optional"
    },
    "openItemsStorage": {
      "doc": "Storage used with openItemsStorageKey. Session storage is recommended for navigation UI state.",
      "type": [
        "\"session\"",
        "\"local\""
      ],
      "defaultValue": "\"session\"",
      "status": "optional"
    },
    "scrollPositionStorageKey": {
      "doc": "Storage key used to persist the nearest ScrollView's vertical position between route navigations.",
      "type": "string",
      "status": "optional"
    },
    "scrollPositionStorage": {
      "doc": "Storage used with scrollPositionStorageKey. Session storage is recommended for navigation UI state.",
      "type": [
        "\"session\"",
        "\"local\""
      ],
      "defaultValue": "\"session\"",
      "status": "optional"
    },
    "scrollSelectedItemIntoView": {
      "doc": "Whether an off-screen selected item is positioned within the nearest ScrollView or browser viewport.",
      "type": "boolean",
      "defaultValue": "true",
      "status": "optional"
    },
    "openOnFind": {
      "doc": "Keeps collapsed accordion content searchable and opens it when matched by the browser's find-in-page functionality.",
      "type": "boolean",
      "defaultValue": "true",
      "status": "optional"
    },
    "activeSection": {
      "doc": "Controlled id of the visible section.",
      "type": "string",
      "status": "optional"
    },
    "defaultActiveSection": {
      "doc": "Id of the initially visible section. Defaults to the section marked defaultActive, then the first section.",
      "type": "string",
      "status": "optional"
    },
    "sectionLabel": {
      "doc": "Screen-reader label for the section dropdown. Defaults to the localized SidebarMenu label.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "selectedItem": {
      "doc": "Controlled id of the selected item or page accordion. A selected page accordion opens when selection initializes or changes, regardless of stored open state, and can then be collapsed manually. Its ancestor accordions and section open automatically, but ancestors can remain collapsed with medium-weight labels to indicate that they contain the current page.",
      "type": "string",
      "status": "optional"
    },
    "defaultSelectedItem": {
      "doc": "Id of the initially selected item or page accordion.",
      "type": "string",
      "status": "optional"
    },
    "[nav attributes]": {
      "doc": "Standard HTML attributes are supported. Provide aria-label or aria-labelledby to name the navigation landmark.",
      "type": "Various",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.Data


```json
{
  "props": {
    "data": {
      "doc": "Recursive items used to render a menu from data.",
      "type": "Array<SidebarMenuItemData>",
      "status": "optional"
    },
    "sections": {
      "doc": "Top-level switchable sections and their recursive items.",
      "type": "Array<SidebarMenuSectionData>",
      "status": "optional"
    },
    "openItems": {
      "doc": "Controlled array of open accordion ids. Selected ancestors are only opened automatically when this property is not provided.",
      "type": "Array<string>",
      "status": "optional"
    },
    "defaultOpenItems": {
      "doc": "Accordion ids that are initially open.",
      "type": "Array<string>",
      "defaultValue": "[]",
      "status": "optional"
    },
    "openItemsStorageKey": {
      "doc": "Storage key used to persist the user's open accordion state between route navigations.",
      "type": "string",
      "status": "optional"
    },
    "openItemsStorage": {
      "doc": "Storage used with openItemsStorageKey. Session storage is recommended for navigation UI state.",
      "type": [
        "\"session\"",
        "\"local\""
      ],
      "defaultValue": "\"session\"",
      "status": "optional"
    },
    "scrollPositionStorageKey": {
      "doc": "Storage key used to persist the nearest ScrollView's vertical position between route navigations.",
      "type": "string",
      "status": "optional"
    },
    "scrollPositionStorage": {
      "doc": "Storage used with scrollPositionStorageKey. Session storage is recommended for navigation UI state.",
      "type": [
        "\"session\"",
        "\"local\""
      ],
      "defaultValue": "\"session\"",
      "status": "optional"
    },
    "scrollSelectedItemIntoView": {
      "doc": "Whether an off-screen selected item is positioned within the nearest ScrollView or browser viewport.",
      "type": "boolean",
      "defaultValue": "true",
      "status": "optional"
    },
    "openOnFind": {
      "doc": "Keeps collapsed accordion content searchable and opens it when matched by the browser's find-in-page functionality.",
      "type": "boolean",
      "defaultValue": "true",
      "status": "optional"
    },
    "activeSection": {
      "doc": "Controlled id of the visible section.",
      "type": "string",
      "status": "optional"
    },
    "defaultActiveSection": {
      "doc": "Id of the initially visible section. Defaults to the section marked defaultActive, then the first section.",
      "type": "string",
      "status": "optional"
    },
    "sectionLabel": {
      "doc": "Screen-reader label for the section dropdown. Defaults to the localized SidebarMenu label.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "selectedItem": {
      "doc": "Controlled id of the selected item or page accordion. A selected page accordion opens when selection initializes or changes, regardless of stored open state, and can then be collapsed manually. Its ancestor accordions and section open automatically, but ancestors can remain collapsed with medium-weight labels to indicate that they contain the current page.",
      "type": "string",
      "status": "optional"
    },
    "defaultSelectedItem": {
      "doc": "Id of the initially selected item or page accordion.",
      "type": "string",
      "status": "optional"
    },
    "[nav attributes]": {
      "doc": "Standard HTML attributes are supported. Provide aria-label or aria-labelledby to name the navigation landmark.",
      "type": "Various",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.Item


```json
{
  "props": {
    "id": {
      "doc": "Unique item id used for selection state.",
      "type": "string",
      "status": "required"
    },
    "text": {
      "doc": "Visible item label.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "children": {
      "doc": "Alternative to text.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "icon": {
      "doc": "Icon displayed before the label.",
      "type": "IconIcon",
      "status": "optional"
    },
    "badge": {
      "doc": "Badge content displayed on the right side.",
      "type": "BadgeProps[\"content\"]",
      "status": "optional"
    },
    "suffix": {
      "doc": "Additional content displayed on the right side before the badge.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "badgeProps": {
      "doc": "Additional properties passed to the Badge component. A nested notification variant is indicated on collapsible ancestor accordions.",
      "type": "Omit<BadgeProps, \"content\" | \"children\">",
      "status": "optional"
    },
    "href": {
      "doc": "Renders the item as a link with this destination.",
      "type": "string",
      "status": "optional"
    },
    "to": {
      "doc": "Router destination passed to element.",
      "type": "string",
      "status": "optional"
    },
    "element": {
      "doc": "Custom link element, such as a router Link component.",
      "type": "React.Element",
      "status": "optional"
    },
    "target": {
      "doc": "Link target attribute.",
      "type": "string",
      "status": "optional"
    },
    "rel": {
      "doc": "Link rel attribute.",
      "type": "string",
      "status": "optional"
    },
    "disabled": {
      "doc": "Disables activation.",
      "type": "boolean",
      "defaultValue": "false",
      "status": "optional"
    },
    "active": {
      "doc": "Marks the item as the current page without managing Root selection state.",
      "type": "boolean",
      "defaultValue": "false",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.Accordion


```json
{
  "props": {
    "id": {
      "doc": "Unique accordion id used by controlled open state and ARIA relationships.",
      "type": "string",
      "status": "required"
    },
    "text": {
      "doc": "Visible accordion label.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "icon": {
      "doc": "Icon displayed before the label.",
      "type": "IconIcon",
      "status": "optional"
    },
    "badge": {
      "doc": "Badge content displayed before the accordion indicator.",
      "type": "BadgeProps[\"content\"]",
      "status": "optional"
    },
    "suffix": {
      "doc": "Additional content displayed before the badge and accordion indicator.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "badgeProps": {
      "doc": "Additional properties passed to the Badge component. A nested notification variant is indicated on collapsible ancestor accordions.",
      "type": "Omit<BadgeProps, \"content\" | \"children\">",
      "status": "optional"
    },
    "href": {
      "doc": "Makes the accordion trigger a page link that also controls expansion.",
      "type": "string",
      "status": "optional"
    },
    "to": {
      "doc": "Router destination passed to element.",
      "type": "string",
      "status": "optional"
    },
    "element": {
      "doc": "Custom link element, such as a router Link component.",
      "type": "React.Element",
      "status": "optional"
    },
    "children": {
      "doc": "Nested items and accordions.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "disabled": {
      "doc": "Disables the accordion trigger.",
      "type": "boolean",
      "defaultValue": "false",
      "status": "optional"
    },
    "open": {
      "doc": "Controlled open state for this accordion.",
      "type": "boolean",
      "status": "optional"
    },
    "defaultOpen": {
      "doc": "Initial local open state.",
      "type": "boolean",
      "defaultValue": "false",
      "status": "optional"
    },
    "collapsible": {
      "doc": "Whether nested items can be collapsed.",
      "type": "boolean",
      "defaultValue": "true",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.Section


```json
{
  "props": {
    "id": {
      "doc": "Unique section id.",
      "type": "string",
      "status": "required"
    },
    "text": {
      "doc": "Label for the section toggle.",
      "type": "React.ReactNode",
      "status": "required"
    },
    "icon": {
      "doc": "Icon shown before the section label.",
      "type": "IconIcon",
      "status": "optional"
    },
    "badge": {
      "doc": "Notification badge displayed on the right side of the dropdown option.",
      "type": "BadgeProps[\"content\"]",
      "status": "optional"
    },
    "badgeProps": {
      "doc": "Additional properties passed to the dropdown option Badge component. The variant defaults to \"notification\".",
      "type": "Omit<BadgeProps, \"content\" | \"children\">",
      "status": "optional"
    },
    "triggerBadge": {
      "doc": "Notification badge displayed on the right side of the selected dropdown trigger.",
      "type": "BadgeProps[\"content\"]",
      "status": "optional"
    },
    "triggerBadgeProps": {
      "doc": "Additional properties passed to the selected dropdown trigger Badge component. The variant defaults to \"notification\".",
      "type": "Omit<BadgeProps, \"content\" | \"children\">",
      "status": "optional"
    },
    "children": {
      "doc": "Menu content shown while the section is active.",
      "type": "React.ReactNode",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.Group


```json
{
  "props": {
    "id": {
      "doc": "Unique id used for selection. Required for linked groups and generated for non-linked groups when omitted.",
      "type": "string",
      "status": "optional"
    },
    "text": {
      "doc": "Visible group title.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "icon": {
      "doc": "Icon shown before a linked group title.",
      "type": "IconIcon",
      "status": "optional"
    },
    "badge": {
      "doc": "Badge content displayed on the right side of a linked group title.",
      "type": "BadgeProps[\"content\"]",
      "status": "optional"
    },
    "suffix": {
      "doc": "Additional content displayed before the badge.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "badgeProps": {
      "doc": "Additional properties passed to the Badge component.",
      "type": "Omit<BadgeProps, \"content\" | \"children\">",
      "status": "optional"
    },
    "children": {
      "doc": "Nested menu content that is always visible.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "href": {
      "doc": "Makes the group title a page link without making it collapsible.",
      "type": "string",
      "status": "optional"
    },
    "to": {
      "doc": "Router destination passed to element.",
      "type": "string",
      "status": "optional"
    },
    "element": {
      "doc": "Custom link element, such as a router Link component.",
      "type": "React.Element",
      "status": "optional"
    },
    "disabled": {
      "doc": "Disables activation of the optional page link.",
      "type": "boolean",
      "defaultValue": "false",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.Header


```json
{
  "props": {
    "text": {
      "doc": "Header text.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "children": {
      "doc": "Alternative to text.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "headingLevel": {
      "doc": "Semantic heading level.",
      "type": "number",
      "defaultValue": "2",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.Divider

Supports standard `HTMLAttributes<HTMLLIElement>`.

## SidebarMenu.PreHydrationScript

Renders the blocking script that restores persisted SidebarMenu state before hydration. It supports standard `ScriptHTMLAttributes<HTMLScriptElement>` except `children` and `dangerouslySetInnerHTML`. Place it after the prerendered menu markup and before the application hydration script.

## SidebarMenu.getPreHydrationScript

Returns the same standalone script as a string for HTML templates assembled outside React.

## SidebarMenu.ResizeHandle


```json
{
  "props": {
    "targetRef": {
      "doc": "Ref to the element being resized. The handle reads this element’s rendered width when dragging starts or an arrow key is pressed.",
      "type": "React.RefObject<HTMLElement>",
      "status": "required"
    },
    "cssProperty": {
      "doc": "CSS custom property that receives the new width as a pixel value, for example `--sidebar-width: 336px`. Use this property in the CSS that sizes the sidebar.",
      "type": "string",
      "defaultValue": "\"--sidebar-menu-width\"",
      "status": "optional"
    },
    "scopeSelector": {
      "doc": "Selector passed to `targetRef.current.closest()` to find where `cssProperty` is set. Use a shared layout ancestor when the sidebar and adjacent content both depend on the width. When omitted, the property is set on the referenced sidebar element.",
      "type": "string",
      "status": "optional"
    },
    "minWidth": {
      "doc": "Smallest width the handle can write, in pixels. Use layout CSS for any additional responsive constraints.",
      "type": "number",
      "defaultValue": "240",
      "status": "optional"
    },
    "maxWidth": {
      "doc": "Largest width the handle can write, in pixels.",
      "type": "number",
      "defaultValue": "560",
      "status": "optional"
    },
    "collapseThreshold": {
      "doc": "Width in pixels at which continued pointer dragging calls onCollapse. Defaults to half of minWidth.",
      "type": "number",
      "defaultValue": "minWidth / 2",
      "status": "optional"
    },
    "step": {
      "doc": "Number of pixels added or removed when pressing ArrowRight or ArrowLeft.",
      "type": "number",
      "defaultValue": "16",
      "status": "optional"
    },
    "largeStep": {
      "doc": "Number of pixels added or removed when pressing Shift together with ArrowRight or ArrowLeft.",
      "type": "number",
      "defaultValue": "48",
      "status": "optional"
    },
    "[button attributes]": {
      "doc": "Standard button attributes are supported. Set `aria-controls` to the id of the resized sidebar. The default accessible label is \"Resize sidebar\".",
      "type": "Various",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.ResponsiveProvider


```json
{
  "props": {
    "breakpoint": {
      "doc": "Maximum viewport width at which the mobile navigation is used. Use an Eufemia breakpoint name or an explicit em value.",
      "type": [
        "MediaQuerySizes",
        "`${number}em`"
      ],
      "defaultValue": "\"medium\"",
      "status": "optional"
    },
    "styleNonce": {
      "doc": "CSP nonce forwarded to first-paint CSS generated for a custom breakpoint.",
      "type": "string",
      "status": "optional"
    },
    "open": {
      "doc": "Controlled Drawer state.",
      "type": "boolean",
      "status": "optional"
    },
    "defaultOpen": {
      "doc": "Initial uncontrolled Drawer state.",
      "type": "boolean",
      "defaultValue": "false",
      "status": "optional"
    },
    "inlineCollapsed": {
      "doc": "Controlled desktop inline navigation state.",
      "type": "boolean",
      "status": "optional"
    },
    "defaultInlineCollapsed": {
      "doc": "Initial uncontrolled desktop inline navigation state.",
      "type": "boolean",
      "defaultValue": "false",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.ResponsiveTrigger


```json
{
  "props": {
    "controls": {
      "doc": "Id of the responsive Drawer controlled by the trigger.",
      "type": "string",
      "defaultValue": "\"sidebar-menu-responsive-drawer\"",
      "status": "optional"
    },
    "inlineControls": {
      "doc": "Id of the desktop inline navigation restored by the trigger.",
      "type": "string",
      "status": "optional"
    },
    "[Button properties]": {
      "doc": "Supports Button properties except aria-expanded and aria-haspopup, which are managed by the responsive navigation.",
      "type": "Various",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.ResponsiveDrawer


```json
{
  "props": {
    "id": {
      "doc": "Id matched by ResponsiveTrigger controls.",
      "type": "string",
      "defaultValue": "\"sidebar-menu-responsive-drawer\"",
      "status": "optional"
    },
    "dialogTitle": {
      "doc": "Accessible name of the Drawer.",
      "type": "React.ReactNode",
      "status": "optional"
    },
    "[Drawer properties]": {
      "doc": "Supports Drawer properties except open, which is managed by ResponsiveProvider.",
      "type": "Various",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.useResponsive

Returns `SidebarMenuResponsiveValue` with:

- `open`, `setOpen`, `toggle`, and `close` for the small-screen Drawer.
- `isSmallScreen` for the active responsive mode.
- `inlineCollapsed`, `collapseInline`, and `restoreInline` for dismissible desktop navigation.

## SidebarMenu.Root Events


```json
{
  "props": {
    "onOpenItemsChange": {
      "doc": "Called whenever an accordion opens or closes with all open accordion ids.",
      "type": "(openItems: string[]) => void",
      "status": "optional"
    },
    "onActiveSectionChange": {
      "doc": "Called when a section toggle is selected.",
      "type": "(sectionId: string) => void",
      "status": "optional"
    },
    "onSelectedItemChange": {
      "doc": "Called when a leaf item is selected.",
      "type": "(itemId: string) => void",
      "status": "optional"
    }
  }
}
```


`SidebarMenu.Data` supports the same events.

## SidebarMenu.ResizeHandle Events


```json
{
  "props": {
    "onCollapse": {
      "doc": "Called when pointer dragging reaches collapseThreshold. Below minWidth, the handle applies resistance to communicate that continued dragging can collapse the sidebar.",
      "type": "() => void",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.ResponsiveProvider Events


```json
{
  "props": {
    "onOpenChange": {
      "doc": "Called whenever the responsive Drawer opens or closes.",
      "type": "(open: boolean) => void",
      "status": "optional"
    },
    "onInlineCollapsedChange": {
      "doc": "Called whenever the desktop inline navigation collapses or restores.",
      "type": "(collapsed: boolean) => void",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.Item Events


```json
{
  "props": {
    "onClick": {
      "doc": "Called when the item is activated after the Root selection state is updated.",
      "type": "(event: React.MouseEvent<HTMLElement>) => void",
      "status": "optional"
    }
  }
}
```


## SidebarMenu.Accordion Events


```json
{
  "props": {
    "onClick": {
      "doc": "Called when the optional page link is activated.",
      "type": "(event: React.MouseEvent<HTMLElement>) => void",
      "status": "optional"
    },
    "onOpenChange": {
      "doc": "Called whenever this accordion opens or closes.",
      "type": "(open: boolean) => void",
      "status": "optional"
    }
  }
}
```
