0.1.0

Sidebar

Sidebars provide persistent app navigation with collapsible, resizable, and mobile-responsive layouts.

Try it

Main content area beside the sidebar.

Usage

Wrap the layout in RuiSidebarProvider. Compose header, content, groups, and menu items with the sidebar sub-components.

import {
  RuiSidebarProvider,
  RuiSidebar,
  RuiSidebarHeader,
  RuiSidebarContent,
  RuiSidebarMenu,
  RuiSidebarMenuItem,
  RuiSidebarMenuButton,
  RuiSidebarInset,
  RuiSidebarTrigger,
} from '@ecopages/radiant-ui/sidebar';
 
<RuiSidebarProvider>
  <RuiSidebar collapsible="icon" defaultOpen>
    <RuiSidebarHeader>Acme</RuiSidebarHeader>
    <RuiSidebarContent>
      <RuiSidebarMenu>
        <RuiSidebarMenuItem>
          <RuiSidebarMenuButton href="/dashboard">Dashboard</RuiSidebarMenuButton>
        </RuiSidebarMenuItem>
      </RuiSidebarMenu>
    </RuiSidebarContent>
  </RuiSidebar>
  <RuiSidebarInset>
    <RuiSidebarTrigger />
    <main>Page content</main>
  </RuiSidebarInset>
</RuiSidebarProvider>

Custom markup

import '@ecopages/radiant-ui/sidebar';
 
<rui-sidebar id="app-sidebar" collapsible="icon" label="Navigation">
  <div data-ref="root" class="rui-sidebar">
    <button data-ref="scrim" type="button" hidden></button>
    <div data-ref="pane" class="rui-sidebar__pane">
      <a href="/dashboard" class="rui-sidebar__menu-button" data-ref="menu-button">Dashboard</a>
    </div>
    <div data-ref="handle" role="separator" hidden></div>
  </div>
</rui-sidebar>

rui-sidebar-trigger stamps [data-ref="button"] and syncs aria-expanded / aria-controls against the sidebar. Each trigger also reflects its controlled sidebar's state, mobile mode, and collapse mode on the trigger host. An explicit controls id targets only that rui-sidebar; if it does not resolve, the trigger stays unattached and does not toggle a nearby sidebar. Without controls, a trigger inside a sidebar uses that ancestor. Placement and glyph styles read only the trigger host and its placement attribute, so an attached trigger follows the sidebar's mobileBreakpoint rather than the viewport width, including when providers are nested.

Before a trigger attaches (the server-rendered paint), placement styles treat viewports below 768px as mobile and read the sidebar whose pane holds the trigger, else the only sidebar of its nearest provider (child combinators, so nested providers stay independent). Other triggers, such as inset triggers shared by two sidebars in one provider, stay visible until they attach.

Collapsible modes

icon collapses to icons only. full hides the sidebar entirely. off keeps it always visible.

Active route matching

Enable matchActive with matchMode to highlight the current page in the menu automatically.

Theming

Sidebar surfaces map to semantic surface roles, never Tailwind palette steps:

PartCSS roles
Pane (rui-sidebar)background
Header / content / footerborder between sections
Menu buttonson-background text
Active menu buttonprimary indicator
Resize handleborder + on-surface grip, primary on hover
Insetbackground

Rounded corners use rounded-container; the collapsible pane in docs layout sits on surface-container-low. Overrides go at the theme layer, not in component CSS.

Accessibility

  • The sidebar renders as a nav landmark with label as its accessible name.
  • Collapsed icon-only mode preserves accessible names on menu buttons.
  • The trigger button exposes whether the sidebar is expanded or collapsed.

API

RuiSidebar (<rui-sidebar>) coordinates the pane, resize, collapse, and active-route matching. Compose sections with the view helpers; RuiSidebarProvider wires the shell.

Attributes (<rui-sidebar>)

AttributeTypeDefaultDescription
variantsidebar · inset · floatingsidebarPane treatment.
sideleft · rightleftWhich side the pane sits on.
collapsibleoff · icon · fulloffCollapse behavior.
defaultWidthnumberInitial width in px.
widthnumberControlled width.
minWidth / maxWidthnumberWidth bounds.
resizablebooleanfalseDraggable resize handle.
defaultOpenbooleantrueInitial open state (desktop).
mobileDefaultOpenbooleanfalseOpen state on mobile when uncontrolled (mount and when the viewport crosses into mobile).
openbooleanControlled open state. Viewport crossings do not override this.
mobileBreakpointnumberWidth below which the mobile rail applies.
labelstringSidebarAccessible name for the nav landmark.
matchActivebooleanfalseHighlight the current route in the menu.
matchModepathname · …pathnameRoute matching mode.
scrollActiveOnMountbooleanfalseScroll the active item into view on mount.
navigationEventsstring''Custom navigation events to listen for.

Light-DOM contract (<rui-sidebar>)

TargetRequiredHost writesAuthor owns
[data-ref="root"]yesdata-state, data-collapsible, …inner shell
[data-ref="pane"]yesaria-label, inertpane content
[data-ref="scrim"]nohiddenmobile overlay button
[data-ref="handle"]nohidden, aria-valuenow, …resize handle
[data-ref="menu-button"]noaria-current, active classmenu links

Host-owned on <rui-sidebar>: role="complementary", data-state, data-pane-width, …

Methods (<rui-sidebar>)

MethodDescription
toggle()Open or close the pane.
setOpen(next)Set open state.
syncActiveLinks(scroll?)Match menu links to the current location when matchActive is set.

Light-DOM contract (<rui-sidebar-trigger>)

TargetRequiredHost writesAuthor owns
[data-ref="button"]yesaria-expanded, aria-controls, aria-label; swaps rui-button--{variant} / --{size}toggle button and its other classes

Attributes (<rui-sidebar-trigger>)

AttributeTypeDefaultDescription
controlsstring''Exact sidebar id. An unresolved id does not fall back to an ancestor sidebar.
button-labelstringToggle sidebarAccessible trigger name (triggerLabel on RuiSidebarTrigger).
placement'' · header · inset''Trigger placement; reflected for placement styles.
variantghost · …ghostButton tone (reuses RuiButton variants).
sizesm · md · lgmdButton size.

The host writes data-sidebar-state, data-sidebar-mobile, and data-sidebar-collapsible on <rui-sidebar-trigger>. data-sidebar-mobile and data-sidebar-collapsible appear once the trigger attaches to its sidebar. Until then, data-sidebar-state matches the server render: collapsed for inset, expanded otherwise.

Events

EventDetailDescription
rui-sidebar-toggleEmitted on every open/closed transition.
rui-sidebar-resize{ width }Emitted on every width change.
rui-sidebar-mobile-changeEmitted when the host flips mobile rail state.

View helpers

ComponentTarget stampedNotes
RuiSidebarProvider—Shell (data-layout) wrapping sidebar + inset.
RuiSidebarHeader / RuiSidebarContent / RuiSidebarFooter—Pane sections; not queried.
RuiSidebarSeparator—Horizontal rule.
RuiSidebarGroup / RuiSidebarGroupLabel / RuiSidebarGroupAction / RuiSidebarGroupHeader—Grouped items.
RuiSidebarMenu / RuiSidebarMenuItem—Menu list + item wrappers.
RuiSidebarMenuButton[data-ref="menu-button"] on <a> or <button>Current-page target when matchActive is set.
RuiSidebarMenuAction—Extra control on a menu item.
RuiSidebarInset—Main content area.
RuiSidebarTrigger[data-ref="button"]Collapse/expand trigger (rui-sidebar-trigger).

CSS classes

Public BEM classes (documented via @cssclass):

ClassDescription
.rui-sidebar · .rui-sidebar__header · .rui-sidebar__content · .rui-sidebar__footerPane structure.
.rui-sidebar__separatorHorizontal rule.
.rui-sidebar__group · .rui-sidebar__group-label · .rui-sidebar__group-action · .rui-sidebar__group-headerGroup structure.
.rui-sidebar__menu · .rui-sidebar__menu-item · .rui-sidebar__menu-button · .rui-sidebar__menu-actionMenu structure.
.rui-sidebar__menu-button--activeCurrent-page marker.
.rui-sidebar__insetMain content area.
.rui-sidebar-provider · .rui-sidebar-provider__site-header · .rui-sidebar-provider__bodyShell structure.

Theme roles

PartCSS variables consumed
Pane / inset--background
Active marker--primary
Resize handle--border, --on-surface, --primary
Surface container (docs)--surface-container-low
Rounded corners--radius-container