Skip to content

Sidebar ​

Sidebar is the shell around a whole application, not a panel inside a page. It owns the split between the navigation and the content, the collapsed state, the width, and the behaviour change at the mobile breakpoint.

ts
import { Sidebar, SidebarContent, SidebarInset, SidebarPanel } from "ropav";

The shell ​

SidebarPanel is the navigation, SidebarInset is everything else. Both are children of Sidebar, which is what lets the layout change without either of them knowing.

sidebar-basic.vue
vue
<script setup lang="ts">
import {
  IconCalendar,
  IconSearch,
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarItem,
  SidebarItemIcon,
  SidebarItemLabel,
  SidebarPanel,
  SidebarTrigger,
} from "ropav";
</script>

<template>
  <div class="frame">
    <Sidebar collapsible="icon">
      <SidebarPanel aria-label="Main">
        <SidebarHeader>Acme</SidebarHeader>

        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupLabel>Workspace</SidebarGroupLabel>

            <SidebarItem>
              <SidebarItemIcon><IconSearch /></SidebarItemIcon>
              <SidebarItemLabel>Search</SidebarItemLabel>
            </SidebarItem>

            <SidebarItem>
              <SidebarItemIcon><IconCalendar /></SidebarItemIcon>
              <SidebarItemLabel>Schedule</SidebarItemLabel>
            </SidebarItem>
          </SidebarGroup>
        </SidebarContent>
      </SidebarPanel>

      <SidebarInset class="inset">
        <SidebarTrigger />
      </SidebarInset>
    </Sidebar>
  </div>
</template>

<style scoped>
.frame {
  height: calc(var(--rp-spacing) * 72);
  width: 100%;
  overflow: hidden;
  border-radius: var(--rp-radius);
  border-style: solid;
  border-width: 1px;
  border-color: var(--rp-border);
}

.inset {
  padding: calc(var(--rp-spacing) * 4);
}
</style>

Collapsing ​

collapsible decides what collapsed means: icon keeps a rail of icons, offcanvas slides the panel away entirely. SidebarTrigger toggles it, and SidebarItemTooltip is what gives an item its name back once the label is hidden.

Below breakpoint the panel becomes a drawer over the content instead, with its own open state — which is why is-mobile-open is separate from is-expanded.

Remembering the layout ​

auto-save-id stores the expanded state and the width, and puts them back on the next visit. They are applied rather than animated into place, so the reader does not watch the layout arrive.

SidebarRail makes the edge draggable, between min-width and max-width, with arrow keys moving by keyboard-step.

Accessibility ​

  • SidebarPanel takes an aria-label — it is a landmark, and a page with more than one navigation region needs them told apart.
  • The rail is a separator with a value, so a keyboard reader can resize it and hear the result.
  • An item marks itself current through aria-current, which is what a router integration sets.

API ​

PropTypeDefaultDescription
autoSaveIdstring—Where the sidebar is remembered between visits, under ropav:sidebar:<id> in localStorage. The wide-viewport state only: whether the panel was left expanded and how wide it was. What the drawer was doing is not worth restoring — it is opened to be used and closed when it is.
breakpointstring"(max-width: 47.99rem)"The viewport at or below which the panel becomes a drawer, as a media query. Written as a query rather than a number so a caller can key it to whatever their layout actually needs — a container, an orientation, a coarse pointer.
collapsible"icon" | "none" | "offcanvas""icon"How the panel gets out of the way. none pins it open.
defaultExpandedbooleantrueWhether the panel starts expanded, when the caller is not controlling it.
defaultMobileOpenbooleanfalse
defaultWidthstring—The width it starts at. Unset, the stylesheet decides and nothing is written inline.
isExpandedboolean—Whether the panel is expanded. Set, the sidebar reports a toggle rather than acting on it.
isMobileOpenboolean—Whether the drawer is showing on a narrow viewport. Held apart from isExpanded on purpose: one flag across both widths would open the drawer already collapsed for anyone who had collapsed the sidebar first.
side"left" | "right""left"
styleStyleValue—Declared rather than left to fall through, because this root binds a style of its own and that style is undefined until someone sets a width. An undefined on a :style binding does not mean "leave it alone" — it takes the attribute off the element, and a caller's style merged into the same binding would go with it, at the moment the width happened to be cleared. Naming it here keeps the two in one binding that is only ever added to.
variant"floating" | "inset" | "sidebar""sidebar"How much chrome the shell paints. floating stands the panel off as a card of its own. inset bares the panel and makes the content beside it the card instead. Both quiet the rail, whose line has nothing left to divide, and both take their gap and corner from --sidebar-card-gap and --sidebar-card-radius. A panel that should merely stop painting says so in one declaration — .rp-sidebar__panel { background-color: transparent } — since nothing else paints that fill and there is no state set for it to flatten. Author CSS is unlayered, so it outranks the rule it replaces without having to say where it sits.
widthstring—The width the expanded panel renders at, as a CSS length.

The default slot receives:

Slot propType
collapsibleSidebarCollapsibleMode
isCollapsedboolean
isExpandedboolean
isMobileboolean
isOpenboolean
sideSidebarSide
EventPayload
expandedChangeboolean
mobileOpenChangeboolean
update:isExpandedboolean
update:isMobileOpenboolean
update:widthstring

SidebarCollapsible

PropTypeDefaultDescription
defaultExpandedbooleanfalseWhether they start showing, when the caller is not controlling it.
isDisabledboolean—
isExpandedboolean—Whether the rows under this item are showing. Set, it reports a toggle rather than acting.

The default slot receives:

Slot propType
isCollapsedboolean
isDisabledboolean
isExpandedboolean
EventPayload
expandedChangeboolean
update:isExpandedboolean

SidebarCollapsibleTrigger

PropTypeDefaultDescription
ariaLabelstring—
isDisabledboolean—

The default slot receives:

Slot propType
isCollapsedboolean
isDisabledboolean
isExpandedboolean
isFocusedboolean
isFocusVisibleboolean
isHoveredboolean
isPressedboolean

SidebarContent

PropTypeDefaultDescription
scrollShadowSizenumber40How deep the fade runs, in pixels. Also how far focus is kept clear of it.
showScrollShadowbooleantrueWhether the scroll region fades at whichever edge has more nav beyond it. On by default, and it costs nothing when there is nothing to say: the fade is a mask keyed on attributes that only appear once the region actually overflows, so a nav short enough to fit is left exactly as it was.

The default slot receives:

Slot propType
isCollapsedboolean
isMobileboolean
isOpenboolean

SidebarFooter

The default slot receives:

Slot propType
isCollapsedboolean
isMobileboolean
isOpenboolean

SidebarGroup

PropTypeDefaultDescription
ariaLabelstring—Names the group where no SidebarGroupLabel does.

The default slot receives:

Slot propType
isCollapsedboolean
isMobileboolean
isOpenboolean

SidebarGroupLabel

The default slot receives:

Slot propType
isCollapsedboolean
isMobileboolean
isOpenboolean

SidebarHeader

The default slot receives:

Slot propType
isCollapsedboolean
isMobileboolean
isOpenboolean

SidebarInset

The default slot receives:

Slot propType
isCollapsedboolean
isMobileboolean
isOpenboolean

SidebarItem

PropTypeDefaultDescription
ariaCurrentLinkCurrent—Marks the item as the one for the current page. "auto" asks the router instead.
ariaDescribedbystring—
ariaLabelstring—
ariaLabelledbystring—
hrefstring—Where the item goes. Without one it renders as a button, for a nav that acts rather than navigates.
isDisabledboolean—
relstring—
routerOptionsunknown—Passed through to the router's navigate alongside the href, for whatever options the application's router accepts. Ignored without a RouterProvider above.
targetstring—

The default slot receives:

Slot propType
isCollapsedboolean
isCurrentboolean
isDisabledboolean
isFocusedboolean
isFocusVisibleboolean
isHoveredboolean
isPressedboolean
EventPayload
pressPressEvent

SidebarItemIcon

Slots: default.

SidebarItemIndicator

Slots: default.

SidebarItemLabel

The default slot receives:

Slot propType
isCollapsedboolean
isMobileboolean
isOpenboolean

SidebarItemTooltip

PropTypeDefaultDescription
closeDelaynumber—How long it stays after the pointer leaves. Left to the theme by default.
delaynumber—How long the pointer has to rest on the item before the tooltip opens. Left to the theme's --rp-tooltip-delay by default, the same as every other tooltip. A rail is a good reason to want this shorter — the label is the only way to read the item, not a hint beside one — but that is a decision about the whole product's tooltips, not about this part.
labelstring—What the tooltip says — the item's own label, which the rail has taken out of sight. Restated rather than read off SidebarItemLabel, because the label is a slot and its text is whatever a caller rendered into it; reaching into the DOM to find out would tie the tooltip to a markup shape neither part promises.

Slots: default.

SidebarItemTrailing

The default slot receives:

Slot propType
isCollapsedboolean
isMobileboolean
isOpenboolean

SidebarPanel

PropTypeDefaultDescription
ariaLabelstring"Sidebar"Accessible name for the landmark. Declared as ariaLabel, not aria-label: Vue normalises prop names, so a hyphenated declaration would never be matched.
ariaLabelledbystring—Ids of the elements that name the landmark.

The default slot receives:

Slot propType
isCollapsedboolean
isMobileboolean
isOpenboolean

SidebarRail

PropTypeDefaultDescription
ariaLabelstring"Resize sidebar" when it resizes, "Toggle sidebar" when it only toggles
ariaLabelledbystring—
isDisabledboolean—Suppresses both gestures.
isResizablebooleanfalseWhether dragging the rail resizes the panel. Off, it is a toggle strip and nothing else.
keyboardLargeStepnumber50How far a shift-arrow press moves it, in pixels.
keyboardStepnumber10How far one arrow press moves the edge, in pixels.
maxWidthnumber480Widest width a drag may reach, in pixels.
minWidthnumber180Narrowest width a drag may reach, in pixels.

The default slot receives:

Slot propType
isCollapsedboolean
isDisabledboolean
isDraggingboolean
isFocusedboolean
isFocusVisibleboolean
isHoveredboolean
isResizableboolean

SidebarSeparator

SidebarSeparator takes nothing but class.

SidebarSubMenu

PropTypeDefaultDescription
ariaLabelstringthe trigger names itNames the list of children.

The default slot receives:

Slot propType
isExpandedboolean

SidebarTrigger

PropTypeDefaultDescription
ariaLabelstring"Toggle sidebar"
isDisabledboolean—

The default slot receives:

Slot propType
isCollapsedboolean
isDisabledboolean
isFocusedboolean
isFocusVisibleboolean
isHoveredboolean
isOpenboolean
isPressedboolean