SidebarPrimary
The primary navigation rail, with a collapsible width.
Usage
import {
SidebarPrimary,
SidebarPrimaryHeader,
SidebarPrimaryContent,
SidebarPrimaryFooter,
SidebarPrimarySection,
SidebarPrimaryMenu,
SidebarPrimaryMenuItem,
SidebarPrimaryMenuItemExtras,
SidebarPrimaryCollapseTrigger,
} from '@acronis-platform/ui-react';SidebarPrimary is the top-level navigation rail. It renders a <nav> and owns
a controlled/uncontrolled expanded (rail width) state. Compose a
SidebarPrimaryHeader, a SidebarPrimaryContent (one or more
SidebarPrimarySections of SidebarPrimaryMenu → SidebarPrimaryMenuItem), and
a SidebarPrimaryFooter. SidebarPrimary and SidebarPrimaryMenuItem are
polymorphic via Base UI's useRender (the render prop) — pass a router link
for menu items. Every menu item carries a leading icon, since that icon is the
only visible part of the row once the rail is collapsed. Themed by the
--ui-sidebar-primary-* tokens.
Examples
A full rail:
<SidebarPrimary>
<SidebarPrimaryHeader logo={<ProductLogo />} collapsedLogo={<ProductMark />} />
<SidebarPrimaryContent>
<SidebarPrimarySection>
<SidebarPrimaryMenu>
<SidebarPrimaryMenuItem href="#" icon={<MonitorIcon />} selected>
Assets
</SidebarPrimaryMenuItem>
<SidebarPrimaryMenuItem href="#" icon={<ShieldCheckIcon />}>
Protection management
</SidebarPrimaryMenuItem>
</SidebarPrimaryMenu>
</SidebarPrimarySection>
</SidebarPrimaryContent>
<SidebarPrimaryFooter>
<SidebarPrimaryMenu>
<SidebarPrimaryCollapseTrigger
icon={<ChevronsLeftIcon />}
expandTooltip="Expand menu"
>
Collapse menu
</SidebarPrimaryCollapseTrigger>
</SidebarPrimaryMenu>
</SidebarPrimaryFooter>
</SidebarPrimary>A trailing affordance goes in the extras prop, not in children — children
is the label, and anything nested there is clipped by its truncation. Figma only
offers size="sm" for the tag slot, so always pass a small Tag:
<SidebarPrimaryMenuItem
href="#"
icon={<InboxIcon />}
extras={
<SidebarPrimaryMenuItemExtras
variant="tag"
tag={
<Tag variant="info" size="sm">
3
</Tag>
}
/>
}
>
My inbox
</SidebarPrimaryMenuItem>Controlled collapse — the trigger above still works, it just reports the next value instead of owning it:
const [expanded, setExpanded] = React.useState(true);
<SidebarPrimary expanded={expanded} onExpandedChange={setExpanded}>
{/* … */}
</SidebarPrimary>icon is required on a menu item, so a row that genuinely has no icon has to opt
out explicitly with noIcon:
<SidebarPrimaryMenuItem href="#" noIcon>
Legacy console
</SidebarPrimaryMenuItem>Behavior
Collapsing the rail
SidebarPrimaryCollapseTriggerflips the rail width itself: it updates the uncontrolled state and always emitsonExpandedChange, so it works in both controlled and uncontrolled modes.- One
iconcovers both states — it rotates 180° when the rail collapses (and the rotation inverts under RTL, so a chevron keeps pointing the way the rail will move). Don't pass a separate "expand" glyph. - Collapsed, labels stay in the DOM as
sr-only, trailing extras are hidden, the header padding shrinks and the logo swaps tocollapsedLogo. The header row keeps a constant height across the transition so the menu below it doesn't jump while the two logo graphics swap. - While collapsed, the trigger shows
expandTooltip(defaults to'Expand') — pass a localized string.
Tooltips on truncated and collapsed labels
- Expanded: a row whose label is actually clipped opens a tooltip with the full label. Rows that fit never open one, and hovering the icon or the extras doesn't open it either — only the label does.
- Collapsed: every row always opens a tooltip with its label, because the
sr-onlylabel itself can't be hovered. - Either way the tooltip is anchored to the whole row rather than the shrinking label, and opens to the side (right in LTR, left in RTL) instead of on top.
- Wrap the rail (or the app) in
TooltipProviderto share open/close delays. In a shadow-DOM host, wrap it inPortalContainerProviderso these internal tooltips portal inside the shadow root — see Shadow DOM.
Right-to-left
- Spacing, text alignment and the collapse icon's rotation are pure CSS
(logical properties plus
ltr:/rtl:variants), so they mirror off the nearestdirancestor. - Tooltip placement is decided in JS from the document root's direction (its
dirattribute or computeddirection). Adir="rtl"set only on an inner wrapper mirrors the layout but leaves tooltips opening to the right.
Accessibility
- The root is a
<nav>landmark;aria-labeldefaults to"Primary"— override it when a page has more than one navigation landmark. - Menus are
<ul>/<li>and rows are native<a>s (or whateverrendersupplies). The current row carriesaria-current="page". - Tab moves between rows; both Enter and Space activate one (Space is handled explicitly so link rows match the button-rendered ones).
- The collapse trigger is a
<button>whosearia-expandedreflects the rail state. - Labels are never removed when collapsed, only visually hidden, so each icon-only row keeps its accessible name.
API Reference
SidebarPrimary
SidebarPrimaryHeader
logo and collapsedLogo let you swap distinct graphics per rail state (e.g.
a full lockup vs. a monogram) instead of resizing/hiding one node. When
neither is given, children renders in both states.
SidebarPrimaryMenuItem
icon looks optional below because the table flattens the prop union; at the
type level an item must supply either icon or noIcon, never neither.
SidebarPrimaryMenuItemExtras
SidebarPrimaryCollapseTrigger
The other structural parts (SidebarPrimaryContent, SidebarPrimaryFooter,
SidebarPrimarySection, SidebarPrimaryMenu) accept the standard attributes
of the element they render.