Acronis UIKit
Components

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

  • SidebarPrimaryCollapseTrigger flips the rail width itself: it updates the uncontrolled state and always emits onExpandedChange, so it works in both controlled and uncontrolled modes.
  • One icon covers 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 to collapsedLogo. The header row keeps a constant height across the transition so the menu below it doesn't jump while the two logo graphics swap. That height is the larger of the two states' padding-y × 2 + logo height sums — with the defaults, max(16×2 + 32, 8×2 + 48) = 64px in both states.
  • Because that clamp is unconditional, logoHeight and collapsedLogoHeight are not independent for row height: each sizes only its own state's logo, but whichever state produces the larger sum sets the row height for both states. Raising logoHeight to 64, for instance, makes the shared row max(16×2 + 32, 8×2 + 64) = 80px, so the collapsed header grows too even though its 32px mark is unchanged. That's deliberate — it's what keeps the row from jumping — but budget for it when overriding either prop.
  • 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-only label 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 TooltipProvider to share open/close delays. In a shadow-DOM host, wrap it in PortalContainerProvider so 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 nearest dir ancestor.
  • Tooltip placement is decided in JS from the document root's direction (its dir attribute or computed direction). A dir="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-label defaults to "Primary" — override it when a page has more than one navigation landmark.
  • Menus are <ul>/<li> and rows are native <a>s (or whatever render supplies). The current row carries aria-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> whose aria-expanded reflects the rail state.
  • Labels are never removed when collapsed, only visually hidden, so each icon-only row keeps its accessible name.

API Reference

SidebarPrimary

Prop

Type

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.

The header sizes the logo slot to the design system default (48px expanded, 32px collapsed) unless overridden. If a tenant-branded logo is authored taller than that default, pass logoHeight/collapsedLogoHeight (in pixels) instead of letting the default height force-downscale it. The anti-"jump" row-height clamp (see Behavior) picks up the override automatically, so the header row still doesn't jump mid-transition — but note that it pins both states to the larger of the two padding-y × 2 + logo height sums. Each prop sizes only its own state's logo, yet either one can grow the other state's header row:

<SidebarPrimaryHeader
  logo={<TenantLogo />}
  collapsedLogo={<TenantMark />}
  logoHeight={64}
  collapsedLogoHeight={40}
/>

Here the expanded sum is 8×2 + 64 = 80px and the collapsed sum is 16×2 + 40 = 72px, so both states get an 80px header row — 16px taller than the 64px row you'd get with no overrides at all (max(16×2 + 32, 8×2 + 48)). logoHeight alone accounts for all of that growth: drop collapsedLogoHeight back to its 32px default and the row is still 80px (max(16×2 + 32, 8×2 + 64)), because the expanded sum is the larger one either way.

The collapsed rail also constrains the mark's width: at 48px wide with 8px of horizontal padding, the collapsed logo has a 32px content box. Give collapsedLogo a portrait-oriented mark — narrower than it is tall — so a taller collapsedLogoHeight doesn't push it past that box.

Prop

Type

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.

Prop

Type

SidebarPrimaryMenuItemExtras

Prop

Type

SidebarPrimaryCollapseTrigger

Prop

Type

The other structural parts (SidebarPrimaryContent, SidebarPrimaryFooter, SidebarPrimarySection, SidebarPrimaryMenu) accept the standard attributes of the element they render.

Edit on GitHub

On this page