Sidebar

The sidebar is a set of parts that build an application shell:

The layout is designed to fill the viewport (min-height: 100dvh). The demos on this page are placed in a box with a fixed height. The ui-sidebar-layout::part(sidebar) rule shown in the code makes the sidebar follow that box instead of the viewport.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sidebar demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/avatar.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/dropdown-menu.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/sidebar.html"></import-element>

  <style>
    .sidebar-demo {
      width: min(calc(var(--spacing) * 160), calc(100vw - var(--spacing) * 28));
      height: calc(var(--spacing) * 96);
      overflow: hidden;
      contain: layout paint;
      border: 1px solid var(--border);
      border-radius: var(--radius-lg);
    }
    .sidebar-demo ui-sidebar-layout {
      height: 100%;
      min-height: 0;
    }
    .sidebar-demo ui-sidebar-layout::part(sidebar) {
      height: 100%;
    }
    .sidebar-demo header {
      display: flex;
      align-items: center;
      gap: calc(var(--spacing) * 2);
      padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
      border-bottom: 1px solid var(--border);
      font-size: var(--text-sm);
      font-weight: var(--font-weight-medium);
    }
    .sidebar-demo .brand {
      display: flex;
      align-items: center;
      gap: calc(var(--spacing) * 2);
      font-size: var(--text-sm);
      font-weight: var(--font-weight-semibold);
      white-space: nowrap;
      overflow: hidden;
    }
    .sidebar-demo .logo {
      display: grid;
      place-items: center;
      flex-shrink: 0;
      width: calc(var(--spacing) * 8);
      height: calc(var(--spacing) * 8);
      border-radius: var(--radius-md);
      background: var(--primary);
      color: var(--primary-foreground);
    }
    .sidebar-demo .body {
      padding: calc(var(--spacing) * 4);
      font-size: var(--text-sm);
      color: var(--muted-foreground);
    }
  </style>
  <div class="sidebar-demo">
    <ui-sidebar-layout>
      <ui-sidebar>
        <ui-sidebar-header>
          <div class="brand"><span class="logo">A</span><span>Acme Inc</span></div>
        </ui-sidebar-header>
        <ui-sidebar-content>
          <ui-sidebar-group>
            <ui-sidebar-group-label>Platform</ui-sidebar-group-label>
            <ui-sidebar-menu>
              <ui-sidebar-menu-item>
                <ui-sidebar-menu-button href="#dashboard" current label="Dashboard">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 10.5 12 3l9 7.5V21H3z" /></svg>
                  Dashboard
                </ui-sidebar-menu-button>
              </ui-sidebar-menu-item>
              <ui-sidebar-menu-item>
                <ui-sidebar-menu-button href="#inbox" label="Inbox">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M22 12h-6l-2 3h-4l-2-3H2M5.5 5h13L22 12v7H2v-7z" /></svg>
                  Inbox
                </ui-sidebar-menu-button>
                <ui-sidebar-menu-badge>12</ui-sidebar-menu-badge>
              </ui-sidebar-menu-item>
              <ui-sidebar-menu-item>
                <ui-sidebar-menu-button href="#calendar" label="Calendar">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="3" y="4" width="18" height="17" rx="2" /><path d="M3 10h18M8 2v4M16 2v4" /></svg>
                  Calendar
                </ui-sidebar-menu-button>
              </ui-sidebar-menu-item>
            </ui-sidebar-menu>
          </ui-sidebar-group>
          <ui-sidebar-group>
            <ui-sidebar-group-label>Settings</ui-sidebar-group-label>
            <ui-sidebar-menu>
              <ui-sidebar-menu-item>
                <ui-sidebar-menu-button href="#team" label="Team">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="9" cy="8" r="4" /><path d="M2 21a7 7 0 0 1 14 0M16 4a4 4 0 0 1 0 8M22 21a7 7 0 0 0-4-6.3" /></svg>
                  Team
                </ui-sidebar-menu-button>
              </ui-sidebar-menu-item>
              <ui-sidebar-menu-item>
                <ui-sidebar-menu-button href="#billing" label="Billing">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="2" y="5" width="20" height="14" rx="2" /><path d="M2 10h20" /></svg>
                  Billing
                </ui-sidebar-menu-button>
              </ui-sidebar-menu-item>
            </ui-sidebar-menu>
          </ui-sidebar-group>
        </ui-sidebar-content>
        <ui-sidebar-footer>
          <ui-dropdown-menu>
            <ui-dropdown-menu-trigger>
              <ui-button variant="ghost" size="sm"><ui-avatar><ui-avatar-fallback>JD</ui-avatar-fallback></ui-avatar> Jane Doe</ui-button>
            </ui-dropdown-menu-trigger>
            <ui-dropdown-menu-content placement="top-start">
              <ui-menu-item value="profile">Profile</ui-menu-item>
              <ui-menu-item value="logout" variant="destructive">Log out</ui-menu-item>
            </ui-dropdown-menu-content>
          </ui-dropdown-menu>
        </ui-sidebar-footer>
      </ui-sidebar>
      <ui-sidebar-inset>
        <header>
          <ui-sidebar-trigger></ui-sidebar-trigger>
          Dashboard
        </header>
        <div class="body">Press the trigger, or Ctrl+B (⌘B on macOS), to collapse the sidebar to icons.</div>
      </ui-sidebar-inset>
    </ui-sidebar-layout>
  </div>
</body>
</html>

Examples

Collapsed by default

Add collapsed to start with the icon rail. Menu buttons shrink to their icon, and labels and badges are hidden. The label attribute of each menu button becomes its tooltip (title). collapsed reflects, so you can style or read the current state.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sidebar demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/sidebar.html"></import-element>

  <div class="sidebar-demo">
    <ui-sidebar-layout collapsed>
      <ui-sidebar>
        <ui-sidebar-header>
          <div class="brand"><span class="logo">A</span><span>Acme Inc</span></div>
        </ui-sidebar-header>
        <ui-sidebar-content>
          <ui-sidebar-group>
            <ui-sidebar-group-label>Platform</ui-sidebar-group-label>
            <ui-sidebar-menu>
              <ui-sidebar-menu-item>
                <ui-sidebar-menu-button href="#dashboard" current label="Dashboard">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 10.5 12 3l9 7.5V21H3z" /></svg>
                  Dashboard
                </ui-sidebar-menu-button>
              </ui-sidebar-menu-item>
              <ui-sidebar-menu-item>
                <ui-sidebar-menu-button href="#inbox" label="Inbox">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M22 12h-6l-2 3h-4l-2-3H2M5.5 5h13L22 12v7H2v-7z" /></svg>
                  Inbox
                </ui-sidebar-menu-button>
                <ui-sidebar-menu-badge>12</ui-sidebar-menu-badge>
              </ui-sidebar-menu-item>
            </ui-sidebar-menu>
          </ui-sidebar-group>
        </ui-sidebar-content>
      </ui-sidebar>
      <ui-sidebar-inset>
        <header>
          <ui-sidebar-trigger></ui-sidebar-trigger>
          Overview
        </header>
        <div class="body">The sidebar starts collapsed.</div>
      </ui-sidebar-inset>
    </ui-sidebar-layout>
  </div>
</body>
</html>

Handling navigation

Each menu button fires a bubbling, cancelable select event when clicked, and its link then navigates as usual unless a listener calls preventDefault(). Listen once on ui-sidebar and keep the current item in state. Hash links like #projects keep the page in place. This is how the dashboard example switches views without reloading.

Previewindex.htmlsidebar-select-demo.html
Workspace

Current page:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sidebar demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/sidebar.html"></import-element>

  <import-element from="./sidebar-select-demo.html"></import-element>
  <sidebar-select-demo></sidebar-select-demo>
</body>
</html>
<custom-element name="sidebar-select-demo">
  <script>
    const view = useState({
      page: "home",
      get items() {
        return [
          { id: "home", href: "#home", label: "Home", current: this.page === "home" },
          { id: "projects", href: "#projects", label: "Projects", current: this.page === "projects" },
          { id: "reports", href: "#reports", label: "Reports", current: this.page === "reports" },
        ];
      },
    });

    function pick(event) {
      view.page = event.target.dataset.id;
    }
  </script>
  <style>
    :host {
      display: block;
      width: min(calc(var(--spacing) * 160), calc(100vw - var(--spacing) * 28));
      height: calc(var(--spacing) * 64);
      overflow: hidden;
      contain: layout paint;
      border: 1px solid var(--border);
      border-radius: var(--radius-lg);
    }
    ui-sidebar-layout {
      height: 100%;
      min-height: 0;
    }
    ui-sidebar-layout::part(sidebar) {
      height: 100%;
    }
    p {
      margin: 0;
      padding: calc(var(--spacing) * 4);
      font-size: var(--text-sm);
    }
  </style>
  <ui-sidebar-layout>
    <ui-sidebar onselect="pick(event)">
      <ui-sidebar-content>
        <ui-sidebar-group>
          <ui-sidebar-group-label>Workspace</ui-sidebar-group-label>
          <ui-sidebar-menu>
            <ui-sidebar-menu-item repeat="#view.items" as="item" key="id">
              <ui-sidebar-menu-button _href="#item.href" _current="#item.current" _label="#item.label" _data-id="#item.id">
                <slot name="#item.label"></slot>
              </ui-sidebar-menu-button>
            </ui-sidebar-menu-item>
          </ui-sidebar-menu>
        </ui-sidebar-group>
      </ui-sidebar-content>
    </ui-sidebar>
    <ui-sidebar-inset>
      <p>Current page: <slot name="#view.page"></slot></p>
    </ui-sidebar-inset>
  </ui-sidebar-layout>
</custom-element>

Remembering the state

Set persist to a localStorage key. The layout saves collapsed under that key on every change and restores it on the next page load.

<ui-sidebar-layout persist="app-sidebar">
  …
</ui-sidebar-layout>

API

Parts

Element Fills Renders
ui-sidebar-layout The shell, with an <aside> for the sidebar, the inset beside it, and a backdrop on small screens.
ui-sidebar the <aside> of ui-sidebar-layout The panel, a full-height column.
ui-sidebar-header the top of ui-sidebar A padded column for a logo or team switcher.
ui-sidebar-content the middle of ui-sidebar A scrolling <nav> around the groups.
ui-sidebar-footer the bottom of ui-sidebar A padded column for a user menu.
ui-sidebar-group ui-sidebar-content A role="group" section, named by its label.
ui-sidebar-group-label ui-sidebar-group The small muted heading. Hidden when collapsed.
ui-sidebar-menu ui-sidebar-group A <ul> of menu items with Up and Down arrow navigation.
ui-sidebar-menu-item the <ul> of ui-sidebar-menu A list item (role="listitem").
ui-sidebar-menu-button ui-sidebar-menu-item The item's <a>, with icon and text. Icons are sized to calc(var(--spacing) * 4). When collapsed, only the icon shows.
ui-sidebar-menu-badge ui-sidebar-menu-item Short text, such as a count, at the end of the item. Hidden when collapsed.
ui-sidebar-trigger anywhere inside ui-sidebar-layout A ghost icon ui-button labelled "Toggle sidebar".
ui-sidebar-inset the main area of ui-sidebar-layout A <main> panel with rounded corners next to the sidebar.

Attributes

ui-sidebar-layout

Attribute Type Default Description
collapsed boolean false Collapses the sidebar to an icon rail (calc(var(--spacing) * 12) wide) and hides labels, badges and group labels. Reflects. Ignored on small screens, where the sidebar is off-canvas instead.
persist string A localStorage key. When set, collapsed is restored from it on connect and saved to it on every change.

ui-sidebar-menu-button

Attribute Type Default Description
href string Link target. Without it, the item is a button activated by Enter and Space.
current boolean false Marks the current page with a highlight, medium weight and aria-current="page" on the link.
label string Sets the link's title, which is the tooltip when the sidebar is collapsed.

The other parts have no attributes.

Properties and methods

Member Element Description
toggle() ui-sidebar-layout On wide screens, flips collapsed. On small screens, opens or closes the off-canvas sidebar.

Events

Event Element Description
select ui-sidebar-menu-button Fires when the button is clicked. Bubbles and is cancelable. preventDefault() stops the link from following its href.
sidebartoggle ui-sidebar-trigger Fires when the trigger is clicked. Bubbles to the enclosing ui-sidebar-layout, which calls toggle().

CSS parts

Part Element Description
sidebar ui-sidebar-layout The <aside> holding the sidebar. Sticky and 100dvh tall by default.
backdrop ui-sidebar-layout The overlay behind the off-canvas sidebar on small screens. Clicking it closes the sidebar.
content ui-sidebar-content The scrolling <nav>.
menu ui-sidebar-menu The <ul>.
button ui-sidebar-menu-button The <a>.
main ui-sidebar-inset The <main> panel.

CSS states and custom properties

Accessibility

Edit this page on GitHub · View as Markdown