Skip to content

Sidebar

Experimental Alpha

This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.

Experimental Component

The Sidebar is an experimental component that departs from AgnosticUI's typical "primitives only" philosophy. Due to its complex nature and shadow DOM encapsulation, it requires some nuanced global CSS to function properly across all scenarios.

Use at your own risk. If you're comfortable with these architectural tradeoffs, feel free to use the component. We welcome contributions to help improve its implementation and reduce external dependencies.

A collapsible sidebar component for navigation with support for expanded/collapsed states, nested navigation, submenus, and responsive behavior. The Sidebar adapts seamlessly across desktop and mobile viewports with optional rail mode (icon-only) on desktop and overlay mode on mobile.

Examples

Vue
Lit
React
Live Preview

Default Sidebar with Navigation

Demonstrates the sidebar with navigation items, submenus, and both expanded/collapsed states. Click the header toggle to collapse into rail mode (icon-only).

Dashboard

Project AlphaProject BetaProject GammaProject AlphaProject BetaProject GammaProfileBillingSecurityPreferencesProfileBillingSecurityPreferences
© 2024 Company

Main Content

Click the header toggle to collapse the sidebar into rail mode.

When collapsed, hover over items with submenus to see them in popovers.

Sidebar with Header Actions

Demonstrates using ag-header-start, ag-header-end, and ag-header-toggle slots for a composable header layout with action buttons.

My Application

© 2024 Company

Header Actions Pattern

The header includes a settings button in the ag-header-end slot.

This allows for flexible header layouts with multiple action buttons.

Sidebar with Built-in Toggle

Using show-header-toggle adds a built-in collapse button automatically.

Built-in Toggle

© 2024 Company

Built-in Header Toggle

No need to provide a custom toggle button—the sidebar includes one automatically.

Disable Compact Mode

With disable-compact-mode, the sidebar has no intermediate collapsed/rail state. It's either fully open (expanded) or completely hidden. This pattern is used in applications like Claude AI Studio.

AI Studio

© 2024 Company

Disable Compact Mode

Use the mobile toggle button to show/hide the sidebar.

Notice there's no collapsed/rail mode—it's either fully visible or completely hidden.

Active Item Tracking

Click navigation items to see the active state change. This demonstrates how to track the current route and apply active styling to both top-level and submenu items.

Navigation

ProfileBillingSecurityProfileBillingSecurity

Active Item Tracking

Current route: /dashboard

Click navigation items to see the active state change.

  • Active styling: Background color and font weight change
  • ARIA current: The active item has aria-current="page" for accessibility
  • Submenu support: Sublinks also track active state
  • Popover sync: Active state works in both inline and popover modes

In a real application, you'd integrate this with your router (Vue Router, etc.) to automatically update the active state based on the current route.

View Vue Code
<template>
  <section>
    <div class="mbe4">
      <h2>Default Sidebar with Navigation</h2>
      <p>
        Demonstrates the sidebar with navigation items, submenus, and both expanded/collapsed states.
        Click the header toggle to collapse into rail mode (icon-only).
      </p>
    </div>
    <div class="mbe6">
      <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
        <VueSidebar
          :open="sidebar1.isOpen"
          :collapsed="sidebar1.isCollapsed"
          :show-mobile-toggle="true"
          @update:open="sidebar1.isOpen = $event"
          @update:collapsed="sidebar1.isCollapsed = $event"
        >
          <h2
            slot="ag-header-start"
            style="margin: 0; font-size: 1.125rem; font-weight: 600;"
          >
            Dashboard
          </h2>
          <button
            type="button"
            slot="ag-header-toggle"
            @click="toggleSidebar1Collapse"
            style="background: none; border: none; padding: 8px 0; cursor: pointer; display: flex; align-items: center; color: inherit;"
            aria-label="Toggle sidebar"
          >
            <svg
              width="20"
              height="20"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <rect
                width="18"
                height="18"
                x="3"
                y="3"
                rx="2"
              />
              <path d="M9 3v18" />
            </svg>
          </button>

          <VueSidebarNav>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button active"
                aria-current="page"
              >
                <Home :size="20" />
                <span class="nav-label">Dashboard</span>
              </button>
            </VueSidebarNavItem>

            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button nav-button-expanded"
                aria-expanded="false"
                @click="handleSubmenuToggle"
              >
                <Folder :size="20" />
                <span class="nav-label">Projects</span>
                <span class="chevron">
                  <ChevronRight :size="16" />
                </span>
              </button>

              <!-- Popover for COLLAPSED mode -->
              <VuePopover
                class="nav-button-collapsed"
                placement="right-start"
                trigger-type="click"
                :distance="8"
                :arrow="true"
                .showHeader="false"
              >
                <button
                  slot="trigger"
                  type="button"
                  class="nav-button"
                >
                  <Folder :size="20" />
                  <span class="nav-label">Projects</span>
                  <span class="collapsed-indicator">
                    <svg
                      viewBox="0 0 8 8"
                      fill="none"
                      xmlns="http://www.w3.org/2000/svg"
                    >
                      <path
                        d="M2 3l2 2 2-2"
                        stroke="currentColor"
                        stroke-width="1"
                        stroke-linecap="round"
                      />
                    </svg>
                  </span>
                </button>
                <VueSidebarNavPopoverSubmenu
                  slot="content"
                  class="popover-submenu"
                >
                  <a
                    href="#"
                    class="nav-sublink"
                    @click.prevent
                  >Project Alpha</a>
                  <a
                    href="#"
                    class="nav-sublink"
                    @click.prevent
                  >Project Beta</a>
                  <a
                    href="#"
                    class="nav-sublink"
                    @click.prevent
                  >Project Gamma</a>
                </VueSidebarNavPopoverSubmenu>
              </VuePopover>

              <!-- Inline submenu for expanded mode -->
              <VueSidebarNavSubmenu>
                <a
                  class="nav-sublink"
                  href="#"
                  @click.prevent
                >Project Alpha</a>
                <a
                  class="nav-sublink"
                  href="#"
                  @click.prevent
                >Project Beta</a>
                <a
                  class="nav-sublink"
                  href="#"
                  @click.prevent
                >Project Gamma</a>
              </VueSidebarNavSubmenu>
            </VueSidebarNavItem>

            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
              >
                <User :size="20" />
                <span class="nav-label">Team</span>
              </button>
            </VueSidebarNavItem>

            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button nav-button-expanded"
                aria-expanded="false"
                @click="handleSubmenuToggle"
              >
                <Settings :size="20" />
                <span class="nav-label">Settings</span>
                <span class="chevron">
                  <ChevronRight :size="16" />
                </span>
              </button>

              <!-- Popover for COLLAPSED mode -->
              <VuePopover
                class="nav-button-collapsed"
                placement="right-start"
                trigger-type="click"
                :distance="8"
                :arrow="true"
                .showHeader="false"
              >
                <button
                  slot="trigger"
                  type="button"
                  class="nav-button"
                >
                  <Settings :size="20" />
                  <span class="nav-label">Settings</span>
                  <span class="collapsed-indicator">
                    <svg
                      viewBox="0 0 8 8"
                      fill="none"
                      xmlns="http://www.w3.org/2000/svg"
                    >
                      <path
                        d="M2 3l2 2 2-2"
                        stroke="currentColor"
                        stroke-width="1"
                        stroke-linecap="round"
                      />
                    </svg>
                  </span>
                </button>
                <VueSidebarNavPopoverSubmenu
                  slot="content"
                  class="popover-submenu"
                >
                  <a
                    href="#"
                    class="nav-sublink"
                    @click.prevent
                  >Profile</a>
                  <a
                    href="#"
                    class="nav-sublink"
                    @click.prevent
                  >Billing</a>
                  <a
                    href="#"
                    class="nav-sublink"
                    @click.prevent
                  >Security</a>
                  <a
                    href="#"
                    class="nav-sublink"
                    @click.prevent
                  >Preferences</a>
                </VueSidebarNavPopoverSubmenu>
              </VuePopover>

              <VueSidebarNavSubmenu>
                <a
                  class="nav-sublink"
                  href="#"
                  @click.prevent
                >Profile</a>
                <a
                  class="nav-sublink"
                  href="#"
                  @click.prevent
                >Billing</a>
                <a
                  class="nav-sublink"
                  href="#"
                  @click.prevent
                >Security</a>
                <a
                  class="nav-sublink"
                  href="#"
                  @click.prevent
                >Preferences</a>
              </VueSidebarNavSubmenu>
            </VueSidebarNavItem>
          </VueSidebarNav>

          <div
            slot="ag-footer"
            style="font-size: 0.875rem; color: var(--ag-text-secondary);"
          >
            © 2024 Company
          </div>
        </VueSidebar>

        <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
          <h1 style="margin-top: 0;">Main Content</h1>
          <p>Click the header toggle to collapse the sidebar into rail mode.</p>
          <p>When collapsed, hover over items with submenus to see them in popovers.</p>
        </main>
      </div>
    </div>

    <div class="mbe4">
      <h2>Sidebar with Header Actions</h2>
      <p>
        Demonstrates using <code>ag-header-start</code>, <code>ag-header-end</code>, and <code>ag-header-toggle</code> slots
        for a composable header layout with action buttons.
      </p>
    </div>
    <div class="mbe6">
      <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
        <VueSidebar
          :open="sidebar2.isOpen"
          :collapsed="sidebar2.isCollapsed"
          :show-mobile-toggle="true"
          @update:open="sidebar2.isOpen = $event"
          @update:collapsed="sidebar2.isCollapsed = $event"
        >
          <h2
            slot="ag-header-start"
            style="margin: 0; font-size: 1.125rem; font-weight: 600;"
          >
            My Application
          </h2>
          <button
            type="button"
            slot="ag-header-end"
            @click="() => {}"
            style="background: none; border: none; padding: 8px; cursor: pointer; display: flex; align-items: center; color: inherit; border-radius: 0.25rem;"
            aria-label="Settings"
            title="Settings"
          >
            <Settings :size="20" />
          </button>
          <button
            type="button"
            slot="ag-header-toggle"
            @click="toggleSidebar2Collapse"
            style="background: none; border: none; padding: 8px 0; cursor: pointer; display: flex; align-items: center; color: inherit;"
            aria-label="Toggle sidebar"
          >
            <svg
              width="20"
              height="20"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <rect
                width="18"
                height="18"
                x="3"
                y="3"
                rx="2"
              />
              <path d="M9 3v18" />
            </svg>
          </button>

          <VueSidebarNav>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button active"
                aria-current="page"
              >
                <Home :size="20" />
                <span class="nav-label">Dashboard</span>
              </button>
            </VueSidebarNavItem>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
              >
                <Folder :size="20" />
                <span class="nav-label">Projects</span>
              </button>
            </VueSidebarNavItem>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
              >
                <User :size="20" />
                <span class="nav-label">Team</span>
              </button>
            </VueSidebarNavItem>
          </VueSidebarNav>

          <div
            slot="ag-footer"
            style="font-size: 0.875rem; color: var(--ag-text-secondary);"
          >
            © 2024 Company
          </div>
        </VueSidebar>

        <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
          <h1 style="margin-top: 0;">Header Actions Pattern</h1>
          <p>The header includes a settings button in the <code>ag-header-end</code> slot.</p>
          <p>This allows for flexible header layouts with multiple action buttons.</p>
        </main>
      </div>
    </div>

    <div class="mbe4">
      <h2>Sidebar with Built-in Toggle</h2>
      <p>
        Using <code>show-header-toggle</code> adds a built-in collapse button automatically.
      </p>
    </div>
    <div class="mbe6">
      <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
        <VueSidebar
          :open="sidebar3.isOpen"
          :collapsed="sidebar3.isCollapsed"
          :show-header-toggle="true"
          :show-mobile-toggle="true"
          @update:open="sidebar3.isOpen = $event"
          @update:collapsed="sidebar3.isCollapsed = $event"
        >
          <h2
            slot="ag-header-start"
            style="margin: 0; font-size: 1.125rem; font-weight: 600;"
          >
            Built-in Toggle
          </h2>

          <VueSidebarNav>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button active"
                aria-current="page"
              >
                <Home :size="20" />
                <span class="nav-label">Dashboard</span>
              </button>
            </VueSidebarNavItem>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
              >
                <Folder :size="20" />
                <span class="nav-label">Projects</span>
              </button>
            </VueSidebarNavItem>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
              >
                <User :size="20" />
                <span class="nav-label">Team</span>
              </button>
            </VueSidebarNavItem>
          </VueSidebarNav>

          <div
            slot="ag-footer"
            style="font-size: 0.875rem; color: var(--ag-text-secondary);"
          >
            © 2024 Company
          </div>
        </VueSidebar>

        <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
          <h1 style="margin-top: 0;">Built-in Header Toggle</h1>
          <p>No need to provide a custom toggle button—the sidebar includes one automatically.</p>
        </main>
      </div>
    </div>

    <div class="mbe4">
      <h2>Disable Compact Mode</h2>
      <p>
        With <code>disable-compact-mode</code>, the sidebar has no intermediate collapsed/rail state.
        It's either fully open (expanded) or completely hidden. This pattern is used in applications like Claude AI Studio.
      </p>
    </div>
    <div class="mbe6">
      <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
        <VueSidebar
          :open="sidebar4.isOpen"
          :disable-compact-mode="true"
          :show-mobile-toggle="true"
          @update:open="sidebar4.isOpen = $event"
        >
          <h2
            slot="ag-header-start"
            style="margin: 0; font-size: 1.125rem; font-weight: 600;"
          >
            AI Studio
          </h2>
          <button
            type="button"
            slot="ag-header-toggle"
            @click="toggleSidebar4Responsive"
            style="background: none; border: none; padding: 8px 0; cursor: pointer; display: flex; align-items: center; color: inherit;"
            aria-label="Toggle sidebar"
          >
            <Command :size="20" />
          </button>

          <VueSidebarNav>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button active"
                aria-current="page"
              >
                <Home :size="20" />
                <span class="nav-label">Dashboard</span>
              </button>
            </VueSidebarNavItem>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
              >
                <Folder :size="20" />
                <span class="nav-label">Projects</span>
              </button>
            </VueSidebarNavItem>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
              >
                <User :size="20" />
                <span class="nav-label">Team</span>
              </button>
            </VueSidebarNavItem>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
              >
                <Settings :size="20" />
                <span class="nav-label">Settings</span>
              </button>
            </VueSidebarNavItem>
          </VueSidebarNav>

          <div
            slot="ag-footer"
            style="font-size: 0.875rem; color: var(--ag-text-secondary);"
          >
            © 2024 Company
          </div>
        </VueSidebar>

        <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
          <h1 style="margin-top: 0;">Disable Compact Mode</h1>
          <p>Use the mobile toggle button to show/hide the sidebar.</p>
          <p>Notice there's no collapsed/rail mode—it's either fully visible or completely hidden.</p>
        </main>
      </div>
    </div>

    <div class="mbe4">
      <h2>Active Item Tracking</h2>
      <p>
        Click navigation items to see the active state change. This demonstrates how to track the current route
        and apply active styling to both top-level and submenu items.
      </p>
    </div>
    <div class="mbe6">
      <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
        <VueSidebar
          :open="sidebar5.isOpen"
          :collapsed="sidebar5.isCollapsed"
          :show-mobile-toggle="true"
          @update:open="sidebar5.isOpen = $event"
          @update:collapsed="sidebar5.isCollapsed = $event"
        >
          <h2
            slot="ag-header-start"
            style="margin: 0; font-size: 1.125rem; font-weight: 600;"
          >
            Navigation
          </h2>
          <button
            type="button"
            slot="ag-header-toggle"
            @click="toggleSidebar5Collapse"
            style="background: none; border: none; padding: 8px 0; cursor: pointer; display: flex; align-items: center; color: inherit;"
            aria-label="Toggle sidebar"
          >
            <svg
              width="20"
              height="20"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <rect
                width="18"
                height="18"
                x="3"
                y="3"
                rx="2"
              />
              <path d="M9 3v18" />
            </svg>
          </button>

          <VueSidebarNav>
            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button active"
                aria-current="page"
                data-route="/dashboard"
                @click="handleNavClick('/dashboard', $event)"
              >
                <Home :size="20" />
                <span class="nav-label">Dashboard</span>
              </button>
            </VueSidebarNavItem>

            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
                data-route="/projects"
                @click="handleNavClick('/projects', $event)"
              >
                <Folder :size="20" />
                <span class="nav-label">Projects</span>
              </button>
            </VueSidebarNavItem>

            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button"
                data-route="/team"
                @click="handleNavClick('/team', $event)"
              >
                <User :size="20" />
                <span class="nav-label">Team</span>
              </button>
            </VueSidebarNavItem>

            <VueSidebarNavItem>
              <button
                type="button"
                class="nav-button nav-button-expanded"
                aria-expanded="false"
                data-route="/settings"
                @click="handleSettingsClick"
              >
                <Settings :size="20" />
                <span class="nav-label">Settings</span>
                <span class="chevron">
                  <ChevronRight :size="16" />
                </span>
              </button>

              <VuePopover
                class="nav-button-collapsed"
                placement="right-start"
                trigger-type="click"
                :distance="8"
                :arrow="true"
                .showHeader="false"
              >
                <button
                  slot="trigger"
                  type="button"
                  class="nav-button"
                  data-route="/settings"
                  @click="handleNavClick('/settings', $event)"
                >
                  <Settings :size="20" />
                  <span class="nav-label">Settings</span>
                  <span class="collapsed-indicator">
                    <svg
                      viewBox="0 0 8 8"
                      fill="none"
                      xmlns="http://www.w3.org/2000/svg"
                    >
                      <path
                        d="M2 3l2 2 2-2"
                        stroke="currentColor"
                        stroke-width="1"
                        stroke-linecap="round"
                      />
                    </svg>
                  </span>
                </button>
                <VueSidebarNavPopoverSubmenu
                  slot="content"
                  class="popover-submenu"
                >
                  <a
                    href="#"
                    class="nav-sublink"
                    data-route="/settings/profile"
                    @click="handleNavClick('/settings/profile', $event)"
                  >Profile</a>
                  <a
                    href="#"
                    class="nav-sublink"
                    data-route="/settings/billing"
                    @click="handleNavClick('/settings/billing', $event)"
                  >Billing</a>
                  <a
                    href="#"
                    class="nav-sublink"
                    data-route="/settings/security"
                    @click="handleNavClick('/settings/security', $event)"
                  >Security</a>
                </VueSidebarNavPopoverSubmenu>
              </VuePopover>

              <VueSidebarNavSubmenu>
                <a
                  class="nav-sublink"
                  href="#"
                  data-route="/settings/profile"
                  @click="handleNavClick('/settings/profile', $event)"
                >Profile</a>
                <a
                  class="nav-sublink"
                  href="#"
                  data-route="/settings/billing"
                  @click="handleNavClick('/settings/billing', $event)"
                >Billing</a>
                <a
                  class="nav-sublink"
                  href="#"
                  data-route="/settings/security"
                  @click="handleNavClick('/settings/security', $event)"
                >Security</a>
              </VueSidebarNavSubmenu>
            </VueSidebarNavItem>
          </VueSidebarNav>
        </VueSidebar>

        <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
          <h1 style="margin-top: 0;">Active Item Tracking</h1>
          <p>Current route: <strong>{{ sidebar5.activeRoute }}</strong></p>
          <p>Click navigation items to see the active state change.</p>
          <ul>
            <li><strong>Active styling:</strong> Background color and font weight change</li>
            <li><strong>ARIA current:</strong> The active item has aria-current="page" for accessibility</li>
            <li><strong>Submenu support:</strong> Sublinks also track active state</li>
            <li><strong>Popover sync:</strong> Active state works in both inline and popover modes</li>
          </ul>
          <p>
            In a real application, you'd integrate this with your router (Vue Router, etc.) to automatically
            update the active state based on the current route.
          </p>
        </main>
      </div>
    </div>
  </section>
</template>

<script>
import VueSidebar from "agnosticui-core/sidebar/vue";
import {
  VueSidebarNav,
  VueSidebarNavItem,
  VueSidebarNavSubmenu,
  VueSidebarNavPopoverSubmenu,
} from "agnosticui-core/sidebar-nav/vue";
import { VuePopover } from "agnosticui-core/popover/vue";
import {
  Home,
  Folder,
  User,
  Settings,
  ChevronRight,
  Command,
} from "lucide-vue-next";

export default {
  name: "SidebarExamples",
  components: {
    VueSidebar,
    VueSidebarNav,
    VueSidebarNavItem,
    VueSidebarNavSubmenu,
    VueSidebarNavPopoverSubmenu,
    VuePopover,
    Home,
    Folder,
    User,
    Settings,
    ChevronRight,
    Command,
  },
  data() {
    return {
      sidebar1: {
        isOpen: true,
        isCollapsed: false,
      },
      sidebar2: {
        isOpen: true,
        isCollapsed: false,
      },
      sidebar3: {
        isOpen: true,
        isCollapsed: false,
      },
      sidebar4: {
        isOpen: true,
      },
      sidebar5: {
        isOpen: true,
        isCollapsed: false,
        activeRoute: "/dashboard",
      },
    };
  },
  mounted() {
    // Inject nav button styles
    const styleId = "sidebar-nav-styles";
    if (!document.getElementById(styleId)) {
      const style = document.createElement("style");
      style.id = styleId;
      style.textContent = `
        /* Overrides VitePress theme - resets .vp-doc h2 styles */
        ag-sidebar h2[slot="ag-header-start"] {
          border-top: none !important;
          padding-top: 0 !important;
          padding-bottom: 0 !important;
          margin-top: 0 !important;
          margin-bottom: 0 !important;
          letter-spacing: normal !important;
          line-height: normal !important;
          overflow-wrap: normal !important;
          white-space: nowrap !important;
        }

        /* Ensure sidebars start in expanded mode in examples */
        ag-sidebar:not([collapsed]) {
          width: var(--ag-sidebar-width, 280px);
        }

        /* Fix disable-compact-mode sidebar visibility */
        ag-sidebar[disable-compact-mode][open] {
          display: flex !important;
          visibility: visible !important;
          opacity: 1 !important;
          width: var(--ag-sidebar-width, 280px) !important;
          transform: none !important;
        }

        /* Overrides VitePress theme - resets .vp-doc a styles */
        ag-sidebar .nav-sublink {
          font-weight: normal !important;
          color: inherit !important;
          text-decoration: none !important;
          text-underline-offset: unset !important;
        }

        .nav-button {
          display: flex;
          align-items: center;
          justify-content: center;
          gap: var(--ag-space-3);
          position: relative;
          padding: var(--ag-space-2) var(--ag-space-3);
          margin-block-end: var(--ag-space-1);
          border: none;
          background: none;
          cursor: pointer;
          width: 100%;
          text-align: left;
          border-radius: var(--ag-radius-sm);
          transition: background var(--ag-fx-duration-sm);
          color: inherit;
        }

        .nav-button svg {
          flex-shrink: 0;
        }

        .nav-button:hover {
          background: var(--ag-background-secondary);
        }

        .nav-button.active {
          background: var(--ag-primary-background);
          color: var(--ag-primary-text);
          font-weight: 500;
        }

        .nav-button .chevron {
          transition: transform var(--ag-fx-duration-md);
          margin-left: auto;
        }

        .nav-button[aria-expanded="true"] .chevron {
          transform: rotate(90deg);
        }

        .nav-button .collapsed-indicator {
          display: none;
          position: absolute;
          bottom: -1px;
          right: -1px;
          width: var(--ag-space-3);
          height: var(--ag-space-3);
        }

        .nav-button .collapsed-indicator svg {
          color: var(--ag-text-muted);
          transform: rotate(315deg);
        }

        .nav-button .nav-label {
          flex-grow: 1;
          overflow: hidden;
          text-overflow: ellipsis;
        }

        .nav-button .nav-label,
        .nav-button .chevron {
          transition: opacity var(--ag-sidebar-transition-duration) var(--ag-sidebar-transition-easing);
          white-space: nowrap;
        }

        ag-sidebar[collapsed] .nav-button {
          width: auto;
          padding: var(--ag-space-2);
        }

        ag-sidebar[collapsed] .nav-button .nav-label,
        ag-sidebar[collapsed] .nav-button .chevron {
          opacity: 0;
          pointer-events: none;
          display: none;
        }

        ag-sidebar[collapsed] .nav-button[aria-expanded] .collapsed-indicator {
          display: block;
        }

        .nav-button-collapsed::part(ag-popover-body) {
          padding: var(--ag-space-1);
        }

        ag-sidebar[collapsed] ag-sidebar-nav-submenu:not(.popover-submenu),
        ag-sidebar:not([collapsed]) ag-popover,
        ag-sidebar[collapsed] .nav-button-expanded,
        ag-sidebar:not([collapsed]) .nav-button-collapsed {
          display: none !important;
        }

        /* Fix popover centering in collapsed mode - ag-popover is inline-block by default */
        ag-sidebar[collapsed] ag-popover.nav-button-collapsed {
          display: block !important;
        }

        .nav-sublink {
          display: block;
          padding: var(--ag-space-2) var(--ag-space-3);
          margin-block-end: var(--ag-space-1);
          color: inherit;
          text-decoration: none;
          border-radius: var(--ag-radius-sm);
          transition: background var(--ag-fx-duration-sm);
        }

        .nav-sublink:hover {
          background: var(--ag-background-secondary);
        }

        /* Active state styles for navigation tracking */
        .nav-button.active,
        .nav-sublink.active {
          background: var(--ag-primary-background);
          color: var(--ag-primary-text);
          font-weight: 500;
        }
      `;
      document.head.appendChild(style);
    }
  },
  methods: {
    toggleSidebar1Collapse(e) {
      const sidebar = e.target.closest("ag-sidebar");
      if (sidebar && sidebar.toggleCollapse) {
        sidebar.toggleCollapse();
      }
    },
    toggleSidebar2Collapse(e) {
      const sidebar = e.target.closest("ag-sidebar");
      if (sidebar && sidebar.toggleCollapse) {
        sidebar.toggleCollapse();
      }
    },
    toggleSidebar4Responsive(e) {
      const sidebar = e.target.closest("ag-sidebar");
      if (sidebar && sidebar.toggleResponsive) {
        sidebar.toggleResponsive();
      }
    },
    handleSubmenuToggle(e) {
      e.preventDefault();
      e.stopPropagation();
      const button = e.currentTarget;
      const navItem = button.closest("ag-sidebar-nav-item");
      const submenu = navItem?.querySelector("ag-sidebar-nav-submenu");

      if (!submenu) return;

      const currentAriaExpanded = button.getAttribute("aria-expanded");
      const isCurrentlyExpanded = currentAriaExpanded === "true";

      if (isCurrentlyExpanded) {
        button.setAttribute("aria-expanded", "false");
        submenu.removeAttribute("open");
      } else {
        button.setAttribute("aria-expanded", "true");
        submenu.setAttribute("open", "");
      }
    },
    toggleSidebar5Collapse(e) {
      const sidebar = e.target.closest("ag-sidebar");
      if (sidebar && sidebar.toggleCollapse) {
        sidebar.toggleCollapse();
      }
    },
    handleNavClick(route, e) {
      e.preventDefault();
      this.sidebar5.activeRoute = route;

      const sidebar = e.target.closest("ag-sidebar");

      // Update top-level nav buttons
      const buttons = sidebar?.querySelectorAll(".nav-button");
      buttons?.forEach((btn) => {
        const isActive = btn.getAttribute("data-route") === route;
        btn.classList.toggle("active", isActive);
        if (isActive) {
          btn.setAttribute("aria-current", "page");
        } else {
          btn.removeAttribute("aria-current");
        }
      });

      // Update sublinks (both inline and in popovers)
      const sublinks = sidebar?.querySelectorAll(".nav-sublink");
      sublinks?.forEach((link) => {
        const isActive = link.getAttribute("data-route") === route;
        link.classList.toggle("active", isActive);
        if (isActive) {
          link.setAttribute("aria-current", "page");
        } else {
          link.removeAttribute("aria-current");
        }
      });
    },
    handleSettingsClick(e) {
      this.handleNavClick("/settings", e);
      this.handleSubmenuToggle(e);
    },
  },
};
</script>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/sidebar';
import 'agnosticui-core/sidebar-nav';
import 'agnosticui-core/popover';

export class SidebarLitExamples extends LitElement {
  static properties = {
    sidebar1: { type: Object },
    sidebar2: { type: Object },
    sidebar3: { type: Object },
    sidebar4: { type: Object },
    sidebar5: { type: Object },
  };

  constructor() {
    super();
    this.sidebar1 = { isOpen: true, isCollapsed: false };
    this.sidebar2 = { isOpen: true, isCollapsed: false };
    this.sidebar3 = { isOpen: true, isCollapsed: false };
    this.sidebar4 = { isOpen: true };
    this.sidebar5 = { isOpen: true, isCollapsed: false, activeRoute: '/dashboard' };
  }

  createRenderRoot() {
    return this;
  }

  firstUpdated() {
    // Inject critical sidebar navigation styles
    const styleId = 'sidebar-nav-styles-lit';
    if (!document.getElementById(styleId)) {
      const style = document.createElement('style');
      style.id = styleId;
      style.textContent = `
        /* CRITICAL: Sidebar component width - must be defined! */
        ag-sidebar {
          width: var(--ag-sidebar-width, 280px);
          transition: width var(--ag-sidebar-transition-duration, 0.3s) var(--ag-sidebar-transition-easing, ease);
          overflow: visible;
        }

        ag-sidebar[collapsed] {
          width: 64px;
        }

        /* Fix disable-compact-mode sidebar visibility */
        ag-sidebar[disable-compact-mode][open] {
          display: flex !important;
          visibility: visible !important;
          opacity: 1 !important;
          width: var(--ag-sidebar-width, 280px) !important;
          transform: none !important;
        }

        /* Navigation button base styles */
        .nav-button {
          display: flex;
          align-items: center;
          gap: var(--ag-space-3);
          position: relative;
          padding: var(--ag-space-2) var(--ag-space-3);
          margin-block-end: var(--ag-space-1);
          border: none;
          background: none;
          cursor: pointer;
          width: 100%;
          text-align: left;
          border-radius: var(--ag-radius-sm);
          transition: background var(--ag-fx-duration-sm);
          color: inherit;
        }

        .nav-button svg {
          flex-shrink: 0;
        }

        .nav-button:hover {
          background: var(--ag-background-secondary);
        }

        .nav-button.active {
          background: var(--ag-primary-background);
          color: var(--ag-primary-text);
          font-weight: 500;
        }

        .nav-label {
          flex-grow: 1;
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
          transition: opacity var(--ag-sidebar-transition-duration) var(--ag-sidebar-transition-easing);
        }

        /* Chevron rotation for expanded submenus */
        .chevron {
          display: flex;
          align-items: center;
          transition: transform var(--ag-fx-duration-md), opacity var(--ag-fx-duration-sm);
          margin-left: auto;
        }

        .nav-button[aria-expanded="true"] .chevron {
          transform: rotate(90deg);
        }

        /* Collapsed indicator - small triangle at 4:30 position */
        .collapsed-indicator {
          display: none;
          position: absolute;
          bottom: -1px;
          right: -1px;
          width: var(--ag-space-3);
          height: var(--ag-space-3);
        }

        .collapsed-indicator svg {
          color: var(--ag-text-muted);
          transform: rotate(315deg);
        }

        /* Show collapsed indicator in collapsed mode for buttons with submenus */
        ag-sidebar[collapsed] .nav-button[aria-expanded] .collapsed-indicator {
          display: block;
        }

        /* CRITICAL: Properly handle collapsed state */
        ag-sidebar[collapsed] .nav-label,
        ag-sidebar[collapsed] .chevron {
          opacity: 0;
          pointer-events: none;
          display: none;
        }

        /* Center icons in collapsed mode */
        ag-sidebar[collapsed] .nav-button {
          width: auto;
          padding: var(--ag-space-2);
        }

        /* Visibility rules for expanded vs collapsed mode */
        ag-sidebar[collapsed] ag-sidebar-nav-submenu:not(.popover-submenu),
        ag-sidebar:not([collapsed]) ag-popover,
        ag-sidebar[collapsed] .nav-button-expanded,
        ag-sidebar:not([collapsed]) .nav-button-collapsed {
          display: none !important;
        }

        /* Fix popover centering in collapsed mode */
        ag-sidebar[collapsed] ag-popover.nav-button-collapsed {
          display: block !important;
        }

        /* CRITICAL: Submenu visibility - hidden by default, visible when open */
        ag-sidebar-nav-submenu {
          display: none;
          overflow: hidden;
        }

        ag-sidebar-nav-submenu[open] {
          display: block;
        }

        /* Submenu link styles */
        .nav-sublink {
          display: block;
          padding: var(--ag-space-2) var(--ag-space-3);
          margin-block-end: var(--ag-space-1);
          color: inherit;
          text-decoration: none;
          border-radius: var(--ag-radius-sm);
          transition: background var(--ag-fx-duration-sm);
        }

        .nav-sublink:hover {
          background: var(--ag-background-secondary);
        }

        /* Active state styles for navigation tracking */
        .nav-button.active,
        .nav-sublink.active {
          background: var(--ag-primary-background);
          color: var(--ag-primary-text);
          font-weight: 500;
        }

        /* Popover submenu styles */
        .nav-button-collapsed::part(ag-popover-body) {
          padding: var(--ag-space-1);
        }

        /* Hide popover header for nav popovers */
        .nav-button-collapsed::part(ag-popover-header) {
          display: none;
        }
      `;
      document.head.appendChild(style);
    }

    // Set up event listeners for submenu toggles
    this.setupSubmenuToggles();
  }

  setupSubmenuToggles() {
    const buttons = this.querySelectorAll('.nav-button-expanded');
    buttons.forEach((button) => {
      button.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();
        const navItem = button.closest('ag-sidebar-nav-item');
        const submenu = navItem?.querySelector('ag-sidebar-nav-submenu');

        if (!submenu) return;

        const currentAriaExpanded = button.getAttribute('aria-expanded');
        const isCurrentlyExpanded = currentAriaExpanded === 'true';

        if (isCurrentlyExpanded) {
          button.setAttribute('aria-expanded', 'false');
          submenu.removeAttribute('open');
        } else {
          button.setAttribute('aria-expanded', 'true');
          submenu.setAttribute('open', '');
        }
      });
    });
  }

  toggleSidebar1Collapse(e) {
    const sidebar = e.target.closest('ag-sidebar');
    if (sidebar && sidebar.toggleCollapse) {
      sidebar.toggleCollapse();
    }
  }

  toggleSidebar2Collapse(e) {
    const sidebar = e.target.closest('ag-sidebar');
    if (sidebar && sidebar.toggleCollapse) {
      sidebar.toggleCollapse();
    }
  }

  toggleSidebar4Responsive(e) {
    const sidebar = e.target.closest('ag-sidebar');
    if (sidebar && sidebar.toggleResponsive) {
      sidebar.toggleResponsive();
    }
  }

  toggleSidebar5Collapse(e) {
    const sidebar = e.target.closest('ag-sidebar');
    if (sidebar && sidebar.toggleCollapse) {
      sidebar.toggleCollapse();
    }
  }

  handleNavClick(route, e) {
    e.preventDefault();
    this.sidebar5.activeRoute = route;
    this.requestUpdate();

    const sidebar = e.target.closest('ag-sidebar');

    // Update top-level nav buttons
    const buttons = sidebar?.querySelectorAll('.nav-button');
    buttons?.forEach((btn) => {
      const isActive = btn.getAttribute('data-route') === route;
      btn.classList.toggle('active', isActive);
      if (isActive) {
        btn.setAttribute('aria-current', 'page');
      } else {
        btn.removeAttribute('aria-current');
      }
    });

    // Update sublinks (both inline and in popovers)
    const sublinks = sidebar?.querySelectorAll('.nav-sublink');
    sublinks?.forEach((link) => {
      const isActive = link.getAttribute('data-route') === route;
      link.classList.toggle('active', isActive);
      if (isActive) {
        link.setAttribute('aria-current', 'page');
      } else {
        link.removeAttribute('aria-current');
      }
    });
  }

  handleSettingsClick(e) {
    this.handleNavClick('/settings', e);
    // Also toggle the submenu
    const button = e.currentTarget;
    const navItem = button.closest('ag-sidebar-nav-item');
    const submenu = navItem?.querySelector('ag-sidebar-nav-submenu');

    if (!submenu) return;

    const currentAriaExpanded = button.getAttribute('aria-expanded');
    const isCurrentlyExpanded = currentAriaExpanded === 'true';

    if (isCurrentlyExpanded) {
      button.setAttribute('aria-expanded', 'false');
      submenu.removeAttribute('open');
    } else {
      button.setAttribute('aria-expanded', 'true');
      submenu.setAttribute('open', '');
    }
  }

  render() {
    return html`
      <section>
        <div class="mbe4">
          <h2>Default Sidebar with Navigation</h2>
          <p>
            Demonstrates the sidebar with navigation items, submenus, and both expanded/collapsed states.
            Click the header toggle to collapse into rail mode (icon-only).
          </p>
        </div>
        <div class="mbe6">
          <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
            <ag-sidebar
              ?open="${this.sidebar1.isOpen}"
              ?collapsed="${this.sidebar1.isCollapsed}"
              show-mobile-toggle
              @toggle="${(e) => { this.sidebar1.isOpen = e.detail; this.requestUpdate(); }}"
              @collapse="${(e) => { this.sidebar1.isCollapsed = e.detail; this.requestUpdate(); }}"
            >
              <h2
                slot="ag-header-start"
                style="margin: 0; font-size: 1.125rem; font-weight: 600;"
              >
                Dashboard
              </h2>
              <button
                type="button"
                slot="ag-header-toggle"
                @click="${this.toggleSidebar1Collapse}"
                style="background: none; border: none; padding: 8px 0; cursor: pointer; display: flex; align-items: center; color: inherit;"
                aria-label="Toggle sidebar"
              >
                <svg
                  width="20"
                  height="20"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                >
                  <rect width="18" height="18" x="3" y="3" rx="2" />
                  <path d="M9 3v18" />
                </svg>
              </button>

              <ag-sidebar-nav>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button active"
                    aria-current="page"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
                      <polyline points="9 22 9 12 15 12 15 22"/>
                    </svg>
                    <span class="nav-label">Dashboard</span>
                  </button>
                </ag-sidebar-nav-item>

                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button nav-button-expanded"
                    aria-expanded="false"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
                    </svg>
                    <span class="nav-label">Projects</span>
                    <span class="chevron">
                      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <polyline points="9 18 15 12 9 6"/>
                      </svg>
                    </span>
                  </button>

                  <!-- Popover for COLLAPSED mode -->
                  <ag-popover
                    class="nav-button-collapsed"
                    placement="right-start"
                    trigger-type="click"
                    distance="8"
                    arrow
                    .showHeader="${false}"
                  >
                    <button
                      slot="trigger"
                      type="button"
                      class="nav-button"
                    >
                      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
                      </svg>
                      <span class="nav-label">Projects</span>
                      <span class="collapsed-indicator">
                        <svg viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
                          <path d="M2 3l2 2 2-2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/>
                        </svg>
                      </span>
                    </button>
                    <ag-sidebar-nav-popover-submenu
                      slot="content"
                      class="popover-submenu"
                    >
                      <a href="#" class="nav-sublink" @click="${(e) => e.preventDefault()}">Project Alpha</a>
                      <a href="#" class="nav-sublink" @click="${(e) => e.preventDefault()}">Project Beta</a>
                      <a href="#" class="nav-sublink" @click="${(e) => e.preventDefault()}">Project Gamma</a>
                    </ag-sidebar-nav-popover-submenu>
                  </ag-popover>

                  <!-- Inline submenu for expanded mode -->
                  <ag-sidebar-nav-submenu>
                    <a class="nav-sublink" href="#" @click="${(e) => e.preventDefault()}">Project Alpha</a>
                    <a class="nav-sublink" href="#" @click="${(e) => e.preventDefault()}">Project Beta</a>
                    <a class="nav-sublink" href="#" @click="${(e) => e.preventDefault()}">Project Gamma</a>
                  </ag-sidebar-nav-submenu>
                </ag-sidebar-nav-item>

                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
                      <circle cx="9" cy="7" r="4"/>
                      <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
                      <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
                    </svg>
                    <span class="nav-label">Team</span>
                  </button>
                </ag-sidebar-nav-item>

                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button nav-button-expanded"
                    aria-expanded="false"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
                      <circle cx="12" cy="12" r="3"/>
                    </svg>
                    <span class="nav-label">Settings</span>
                    <span class="chevron">
                      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <polyline points="9 18 15 12 9 6"/>
                      </svg>
                    </span>
                  </button>

                  <!-- Popover for COLLAPSED mode -->
                  <ag-popover
                    class="nav-button-collapsed"
                    placement="right-start"
                    trigger-type="click"
                    distance="8"
                    arrow
                    .showHeader="${false}"
                  >
                    <button
                      slot="trigger"
                      type="button"
                      class="nav-button"
                    >
                      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
                        <circle cx="12" cy="12" r="3"/>
                      </svg>
                      <span class="nav-label">Settings</span>
                      <span class="collapsed-indicator">
                        <svg viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
                          <path d="M2 3l2 2 2-2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/>
                        </svg>
                      </span>
                    </button>
                    <ag-sidebar-nav-popover-submenu
                      slot="content"
                      class="popover-submenu"
                    >
                      <a href="#" class="nav-sublink" @click="${(e) => e.preventDefault()}">Profile</a>
                      <a href="#" class="nav-sublink" @click="${(e) => e.preventDefault()}">Billing</a>
                      <a href="#" class="nav-sublink" @click="${(e) => e.preventDefault()}">Security</a>
                      <a href="#" class="nav-sublink" @click="${(e) => e.preventDefault()}">Preferences</a>
                    </ag-sidebar-nav-popover-submenu>
                  </ag-popover>

                  <ag-sidebar-nav-submenu>
                    <a class="nav-sublink" href="#" @click="${(e) => e.preventDefault()}">Profile</a>
                    <a class="nav-sublink" href="#" @click="${(e) => e.preventDefault()}">Billing</a>
                    <a class="nav-sublink" href="#" @click="${(e) => e.preventDefault()}">Security</a>
                    <a class="nav-sublink" href="#" @click="${(e) => e.preventDefault()}">Preferences</a>
                  </ag-sidebar-nav-submenu>
                </ag-sidebar-nav-item>
              </ag-sidebar-nav>

              <div
                slot="ag-footer"
                style="font-size: 0.875rem; color: var(--ag-text-secondary);"
              >
                © 2024 Company
              </div>
            </ag-sidebar>

            <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
              <h1 style="margin-top: 0;">Main Content</h1>
              <p>Click the header toggle to collapse the sidebar into rail mode.</p>
              <p>When collapsed, hover over items with submenus to see them in popovers.</p>
            </main>
          </div>
        </div>

        <div class="mbe4">
          <h2>Sidebar with Header Actions</h2>
          <p>
            Demonstrates using <code>ag-header-start</code>, <code>ag-header-end</code>, and <code>ag-header-toggle</code> slots
            for a composable header layout with action buttons.
          </p>
        </div>
        <div class="mbe6">
          <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
            <ag-sidebar
              ?open="${this.sidebar2.isOpen}"
              ?collapsed="${this.sidebar2.isCollapsed}"
              show-mobile-toggle
              @toggle="${(e) => { this.sidebar2.isOpen = e.detail; this.requestUpdate(); }}"
              @collapse="${(e) => { this.sidebar2.isCollapsed = e.detail; this.requestUpdate(); }}"
            >
              <h2
                slot="ag-header-start"
                style="margin: 0; font-size: 1.125rem; font-weight: 600;"
              >
                My Application
              </h2>
              <button
                type="button"
                slot="ag-header-end"
                @click="${() => {}}"
                style="background: none; border: none; padding: 8px; cursor: pointer; display: flex; align-items: center; color: inherit; border-radius: 0.25rem;"
                aria-label="Settings"
                title="Settings"
              >
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                  <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
                  <circle cx="12" cy="12" r="3"/>
                </svg>
              </button>
              <button
                type="button"
                slot="ag-header-toggle"
                @click="${this.toggleSidebar2Collapse}"
                style="background: none; border: none; padding: 8px 0; cursor: pointer; display: flex; align-items: center; color: inherit;"
                aria-label="Toggle sidebar"
              >
                <svg
                  width="20"
                  height="20"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                >
                  <rect width="18" height="18" x="3" y="3" rx="2" />
                  <path d="M9 3v18" />
                </svg>
              </button>

              <ag-sidebar-nav>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button active"
                    aria-current="page"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
                      <polyline points="9 22 9 12 15 12 15 22"/>
                    </svg>
                    <span class="nav-label">Dashboard</span>
                  </button>
                </ag-sidebar-nav-item>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
                    </svg>
                    <span class="nav-label">Projects</span>
                  </button>
                </ag-sidebar-nav-item>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
                      <circle cx="9" cy="7" r="4"/>
                      <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
                      <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
                    </svg>
                    <span class="nav-label">Team</span>
                  </button>
                </ag-sidebar-nav-item>
              </ag-sidebar-nav>

              <div
                slot="ag-footer"
                style="font-size: 0.875rem; color: var(--ag-text-secondary);"
              >
                © 2024 Company
              </div>
            </ag-sidebar>

            <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
              <h1 style="margin-top: 0;">Header Actions Pattern</h1>
              <p>The header includes a settings button in the <code>ag-header-end</code> slot.</p>
              <p>This allows for flexible header layouts with multiple action buttons.</p>
            </main>
          </div>
        </div>

        <div class="mbe4">
          <h2>Sidebar with Built-in Toggle</h2>
          <p>
            Using <code>show-header-toggle</code> adds a built-in collapse button automatically.
          </p>
        </div>
        <div class="mbe6">
          <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
            <ag-sidebar
              ?open="${this.sidebar3.isOpen}"
              ?collapsed="${this.sidebar3.isCollapsed}"
              show-header-toggle
              show-mobile-toggle
              @toggle="${(e) => { this.sidebar3.isOpen = e.detail; this.requestUpdate(); }}"
              @collapse="${(e) => { this.sidebar3.isCollapsed = e.detail; this.requestUpdate(); }}"
            >
              <h2
                slot="ag-header-start"
                style="margin: 0; font-size: 1.125rem; font-weight: 600;"
              >
                Built-in Toggle
              </h2>

              <ag-sidebar-nav>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button active"
                    aria-current="page"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
                      <polyline points="9 22 9 12 15 12 15 22"/>
                    </svg>
                    <span class="nav-label">Dashboard</span>
                  </button>
                </ag-sidebar-nav-item>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
                    </svg>
                    <span class="nav-label">Projects</span>
                  </button>
                </ag-sidebar-nav-item>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
                      <circle cx="9" cy="7" r="4"/>
                      <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
                      <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
                    </svg>
                    <span class="nav-label">Team</span>
                  </button>
                </ag-sidebar-nav-item>
              </ag-sidebar-nav>

              <div
                slot="ag-footer"
                style="font-size: 0.875rem; color: var(--ag-text-secondary);"
              >
                © 2024 Company
              </div>
            </ag-sidebar>

            <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
              <h1 style="margin-top: 0;">Built-in Header Toggle</h1>
              <p>No need to provide a custom toggle button—the sidebar includes one automatically.</p>
            </main>
          </div>
        </div>

        <div class="mbe4">
          <h2>Disable Compact Mode</h2>
          <p>
            With <code>disable-compact-mode</code>, the sidebar has no intermediate collapsed/rail state.
            It's either fully open (expanded) or completely hidden. This pattern is used in applications like Claude AI Studio.
          </p>
        </div>
        <div class="mbe6">
          <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
            <ag-sidebar
              ?open="${this.sidebar4.isOpen}"
              disable-compact-mode
              show-mobile-toggle
              @toggle="${(e) => { this.sidebar4.isOpen = e.detail; this.requestUpdate(); }}"
            >
              <h2
                slot="ag-header-start"
                style="margin: 0; font-size: 1.125rem; font-weight: 600;"
              >
                AI Studio
              </h2>
              <button
                type="button"
                slot="ag-header-toggle"
                @click="${this.toggleSidebar4Responsive}"
                style="background: none; border: none; padding: 8px 0; cursor: pointer; display: flex; align-items: center; color: inherit;"
                aria-label="Toggle sidebar"
              >
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                  <path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"/>
                </svg>
              </button>

              <ag-sidebar-nav>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button active"
                    aria-current="page"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
                      <polyline points="9 22 9 12 15 12 15 22"/>
                    </svg>
                    <span class="nav-label">Dashboard</span>
                  </button>
                </ag-sidebar-nav-item>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
                    </svg>
                    <span class="nav-label">Projects</span>
                  </button>
                </ag-sidebar-nav-item>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
                      <circle cx="9" cy="7" r="4"/>
                      <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
                      <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
                    </svg>
                    <span class="nav-label">Team</span>
                  </button>
                </ag-sidebar-nav-item>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
                      <circle cx="12" cy="12" r="3"/>
                    </svg>
                    <span class="nav-label">Settings</span>
                  </button>
                </ag-sidebar-nav-item>
              </ag-sidebar-nav>

              <div
                slot="ag-footer"
                style="font-size: 0.875rem; color: var(--ag-text-secondary);"
              >
                © 2024 Company
              </div>
            </ag-sidebar>

            <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
              <h1 style="margin-top: 0;">Disable Compact Mode</h1>
              <p>Use the mobile toggle button to show/hide the sidebar.</p>
              <p>Notice there's no collapsed/rail mode—it's either fully visible or completely hidden.</p>
            </main>
          </div>
        </div>

        <div class="mbe4">
          <h2>Active Item Tracking</h2>
          <p>
            Click navigation items to see the active state change. This demonstrates how to track the current route
            and apply active styling to both top-level and submenu items.
          </p>
        </div>
        <div class="mbe6">
          <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
            <ag-sidebar
              ?open="${this.sidebar5.isOpen}"
              ?collapsed="${this.sidebar5.isCollapsed}"
              show-mobile-toggle
              @toggle="${(e) => { this.sidebar5.isOpen = e.detail; this.requestUpdate(); }}"
              @collapse="${(e) => { this.sidebar5.isCollapsed = e.detail; this.requestUpdate(); }}"
            >
              <h2
                slot="ag-header-start"
                style="margin: 0; font-size: 1.125rem; font-weight: 600;"
              >
                Navigation
              </h2>
              <button
                type="button"
                slot="ag-header-toggle"
                @click="${this.toggleSidebar5Collapse}"
                style="background: none; border: none; padding: 8px 0; cursor: pointer; display: flex; align-items: center; color: inherit;"
                aria-label="Toggle sidebar"
              >
                <svg
                  width="20"
                  height="20"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                >
                  <rect width="18" height="18" x="3" y="3" rx="2" />
                  <path d="M9 3v18" />
                </svg>
              </button>

              <ag-sidebar-nav>
                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button active"
                    aria-current="page"
                    data-route="/dashboard"
                    @click="${(e) => this.handleNavClick('/dashboard', e)}"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
                      <polyline points="9 22 9 12 15 12 15 22"/>
                    </svg>
                    <span class="nav-label">Dashboard</span>
                  </button>
                </ag-sidebar-nav-item>

                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                    data-route="/projects"
                    @click="${(e) => this.handleNavClick('/projects', e)}"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
                    </svg>
                    <span class="nav-label">Projects</span>
                  </button>
                </ag-sidebar-nav-item>

                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button"
                    data-route="/team"
                    @click="${(e) => this.handleNavClick('/team', e)}"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
                      <circle cx="9" cy="7" r="4"/>
                      <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
                      <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
                    </svg>
                    <span class="nav-label">Team</span>
                  </button>
                </ag-sidebar-nav-item>

                <ag-sidebar-nav-item>
                  <button
                    type="button"
                    class="nav-button nav-button-expanded"
                    aria-expanded="false"
                    data-route="/settings"
                    @click="${this.handleSettingsClick}"
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
                      <circle cx="12" cy="12" r="3"/>
                    </svg>
                    <span class="nav-label">Settings</span>
                    <span class="chevron">
                      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <polyline points="9 18 15 12 9 6"/>
                      </svg>
                    </span>
                  </button>

                  <ag-popover
                    class="nav-button-collapsed"
                    placement="right-start"
                    trigger-type="click"
                    distance="8"
                    arrow
                    .showHeader="${false}"
                  >
                    <button
                      slot="trigger"
                      type="button"
                      class="nav-button"
                      data-route="/settings"
                      @click="${(e) => this.handleNavClick('/settings', e)}"
                    >
                      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
                        <circle cx="12" cy="12" r="3"/>
                      </svg>
                      <span class="nav-label">Settings</span>
                      <span class="collapsed-indicator">
                        <svg viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
                          <path d="M2 3l2 2 2-2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/>
                        </svg>
                      </span>
                    </button>
                    <ag-sidebar-nav-popover-submenu
                      slot="content"
                      class="popover-submenu"
                    >
                      <a
                        href="#"
                        class="nav-sublink"
                        data-route="/settings/profile"
                        @click="${(e) => this.handleNavClick('/settings/profile', e)}"
                      >Profile</a>
                      <a
                        href="#"
                        class="nav-sublink"
                        data-route="/settings/billing"
                        @click="${(e) => this.handleNavClick('/settings/billing', e)}"
                      >Billing</a>
                      <a
                        href="#"
                        class="nav-sublink"
                        data-route="/settings/security"
                        @click="${(e) => this.handleNavClick('/settings/security', e)}"
                      >Security</a>
                    </ag-sidebar-nav-popover-submenu>
                  </ag-popover>

                  <ag-sidebar-nav-submenu>
                    <a
                      class="nav-sublink"
                      href="#"
                      data-route="/settings/profile"
                      @click="${(e) => this.handleNavClick('/settings/profile', e)}"
                    >Profile</a>
                    <a
                      class="nav-sublink"
                      href="#"
                      data-route="/settings/billing"
                      @click="${(e) => this.handleNavClick('/settings/billing', e)}"
                    >Billing</a>
                    <a
                      class="nav-sublink"
                      href="#"
                      data-route="/settings/security"
                      @click="${(e) => this.handleNavClick('/settings/security', e)}"
                    >Security</a>
                  </ag-sidebar-nav-submenu>
                </ag-sidebar-nav-item>
              </ag-sidebar-nav>
            </ag-sidebar>

            <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
              <h1 style="margin-top: 0;">Active Item Tracking</h1>
              <p>Current route: <strong>${this.sidebar5.activeRoute}</strong></p>
              <p>Click navigation items to see the active state change.</p>
              <ul>
                <li><strong>Active styling:</strong> Background color and font weight change</li>
                <li><strong>ARIA current:</strong> The active item has aria-current="page" for accessibility</li>
                <li><strong>Submenu support:</strong> Sublinks also track active state</li>
                <li><strong>Popover sync:</strong> Active state works in both inline and popover modes</li>
              </ul>
              <p>
                In a real application, you'd integrate this with your router (Vue Router, etc.) to automatically
                update the active state based on the current route.
              </p>
            </main>
          </div>
        </div>
      </section>
    `;
  }
}

customElements.define('sidebar-lit-examples', SidebarLitExamples);

Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.

View React Code
import { ReactSidebar } from "agnosticui-core/sidebar/react";
import {
  ReactSidebarNav,
  ReactSidebarNavItem,
  ReactSidebarNavSubmenu,
  ReactSidebarNavPopoverSubmenu,
} from "agnosticui-core/sidebar-nav/react";
import { ReactPopover } from "agnosticui-core/popover/react";
import { useState, useEffect } from "react";

// SVG Icons as inline components
const HomeIcon = () => (
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
    <polyline points="9 22 9 12 15 12 15 22"/>
  </svg>
);

const FolderIcon = () => (
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
  </svg>
);

const UserIcon = () => (
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
    <circle cx="9" cy="7" r="4"/>
    <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
    <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
  </svg>
);

const SettingsIcon = () => (
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
    <circle cx="12" cy="12" r="3"/>
  </svg>
);

const ChevronRightIcon = () => (
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="9 18 15 12 9 6"/>
  </svg>
);

const PanelIcon = () => (
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <rect width="18" height="18" x="3" y="3" rx="2" />
    <path d="M9 3v18" />
  </svg>
);

const CommandIcon = () => (
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"/>
  </svg>
);

export default function SidebarReactExamples() {
  const [sidebar1, setSidebar1] = useState({ isOpen: true, isCollapsed: false });
  const [sidebar2, setSidebar2] = useState({ isOpen: true, isCollapsed: false });
  const [sidebar3, setSidebar3] = useState({ isOpen: true, isCollapsed: false });
  const [sidebar4, setSidebar4] = useState({ isOpen: true });
  const [sidebar5, setSidebar5] = useState({ isOpen: true, isCollapsed: false, activeRoute: '/dashboard' });

  useEffect(() => {
    // Inject critical sidebar navigation styles
    const styleId = 'sidebar-nav-styles-react';
    if (!document.getElementById(styleId)) {
      const style = document.createElement('style');
      style.id = styleId;
      style.textContent = `
        /* CRITICAL: Sidebar component width - must be defined! */
        ag-sidebar {
          width: var(--ag-sidebar-width, 280px);
          transition: width var(--ag-sidebar-transition-duration, 0.3s) var(--ag-sidebar-transition-easing, ease);
          overflow: visible;
        }

        ag-sidebar[collapsed] {
          width: 64px;
        }

        /* Fix disable-compact-mode sidebar visibility */
        ag-sidebar[disable-compact-mode][open] {
          display: flex !important;
          visibility: visible !important;
          opacity: 1 !important;
          width: var(--ag-sidebar-width, 280px) !important;
          transform: none !important;
        }

        /* Navigation button base styles */
        .nav-button {
          display: flex;
          align-items: center;
          gap: var(--ag-space-3);
          position: relative;
          padding: var(--ag-space-2) var(--ag-space-3);
          margin-block-end: var(--ag-space-1);
          border: none;
          background: none;
          cursor: pointer;
          width: 100%;
          text-align: left;
          border-radius: var(--ag-radius-sm);
          transition: background var(--ag-fx-duration-sm);
          color: inherit;
        }

        .nav-button svg {
          flex-shrink: 0;
        }

        .nav-button:hover {
          background: var(--ag-background-secondary);
        }

        .nav-button.active {
          background: var(--ag-primary-background);
          color: var(--ag-primary-text);
          font-weight: 500;
        }

        .nav-label {
          flex-grow: 1;
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
          transition: opacity var(--ag-sidebar-transition-duration) var(--ag-sidebar-transition-easing);
        }

        /* Chevron rotation for expanded submenus */
        .chevron {
          display: flex;
          align-items: center;
          transition: transform var(--ag-fx-duration-md), opacity var(--ag-fx-duration-sm);
          margin-left: auto;
        }

        .nav-button[aria-expanded="true"] .chevron {
          transform: rotate(90deg);
        }

        /* Collapsed indicator - small triangle at 4:30 position */
        .collapsed-indicator {
          display: none;
          position: absolute;
          bottom: -1px;
          right: -1px;
          width: var(--ag-space-3);
          height: var(--ag-space-3);
        }

        .collapsed-indicator svg {
          color: var(--ag-text-muted);
          transform: rotate(315deg);
        }

        /* Show collapsed indicator in collapsed mode for buttons with submenus */
        ag-sidebar[collapsed] .nav-button[aria-expanded] .collapsed-indicator {
          display: block;
        }

        /* CRITICAL: Properly handle collapsed state */
        ag-sidebar[collapsed] .nav-label,
        ag-sidebar[collapsed] .chevron {
          opacity: 0;
          pointer-events: none;
          display: none;
        }

        /* Center icons in collapsed mode */
        ag-sidebar[collapsed] .nav-button {
          width: auto;
          padding: var(--ag-space-2);
        }

        /* Visibility rules for expanded vs collapsed mode */
        ag-sidebar[collapsed] ag-sidebar-nav-submenu:not(.popover-submenu),
        ag-sidebar:not([collapsed]) ag-popover,
        ag-sidebar[collapsed] .nav-button-expanded,
        ag-sidebar:not([collapsed]) .nav-button-collapsed {
          display: none !important;
        }

        /* Fix popover centering in collapsed mode */
        ag-sidebar[collapsed] ag-popover.nav-button-collapsed {
          display: block !important;
        }

        /* CRITICAL: Submenu visibility - hidden by default, visible when open */
        ag-sidebar-nav-submenu {
          display: none;
          overflow: hidden;
        }

        ag-sidebar-nav-submenu[open] {
          display: block;
        }

        /* Submenu link styles */
        .nav-sublink {
          display: block;
          padding: var(--ag-space-2) var(--ag-space-3);
          margin-block-end: var(--ag-space-1);
          color: inherit;
          text-decoration: none;
          border-radius: var(--ag-radius-sm);
          transition: background var(--ag-fx-duration-sm);
        }

        .nav-sublink:hover {
          background: var(--ag-background-secondary);
        }

        /* Active state styles for navigation tracking */
        .nav-button.active,
        .nav-sublink.active {
          background: var(--ag-primary-background);
          color: var(--ag-primary-text);
          font-weight: 500;
        }

        /* Popover submenu styles */
        .nav-button-collapsed::part(ag-popover-body) {
          padding: var(--ag-space-1);
        }

        /* Hide popover header for nav popovers */
        .nav-button-collapsed::part(ag-popover-header) {
          display: none;
        }
      `;
      document.head.appendChild(style);
    }

    // Set up event listeners for submenu toggles
    const buttons = document.querySelectorAll('.nav-button-expanded');
    buttons.forEach((button) => {
      button.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();
        const navItem = button.closest('ag-sidebar-nav-item');
        const submenu = navItem?.querySelector('ag-sidebar-nav-submenu');

        if (!submenu) return;

        const currentAriaExpanded = button.getAttribute('aria-expanded');
        const isCurrentlyExpanded = currentAriaExpanded === 'true';

        if (isCurrentlyExpanded) {
          button.setAttribute('aria-expanded', 'false');
          submenu.removeAttribute('open');
        } else {
          button.setAttribute('aria-expanded', 'true');
          submenu.setAttribute('open', '');
        }
      });
    });
  }, []);

  const toggleSidebar1Collapse = () => {
    setSidebar1({ ...sidebar1, isCollapsed: !sidebar1.isCollapsed });
  };

  const toggleSidebar2Collapse = () => {
    setSidebar2({ ...sidebar2, isCollapsed: !sidebar2.isCollapsed });
  };

  const toggleSidebar4Responsive = () => {
    setSidebar4({ ...sidebar4, isOpen: !sidebar4.isOpen });
  };

  const toggleSidebar5Collapse = () => {
    setSidebar5({ ...sidebar5, isCollapsed: !sidebar5.isCollapsed });
  };

  const handleNavClick = (route, e) => {
    e.preventDefault();
    setSidebar5({ ...sidebar5, activeRoute: route });

    const sidebar = e.target.closest('ag-sidebar');

    // Update top-level nav buttons
    const buttons = sidebar?.querySelectorAll('.nav-button');
    buttons?.forEach((btn) => {
      const isActive = btn.getAttribute('data-route') === route;
      btn.classList.toggle('active', isActive);
      if (isActive) {
        btn.setAttribute('aria-current', 'page');
      } else {
        btn.removeAttribute('aria-current');
      }
    });

    // Update sublinks (both inline and in popovers)
    const sublinks = sidebar?.querySelectorAll('.nav-sublink');
    sublinks?.forEach((link) => {
      const isActive = link.getAttribute('data-route') === route;
      link.classList.toggle('active', isActive);
      if (isActive) {
        link.setAttribute('aria-current', 'page');
      } else {
        link.removeAttribute('aria-current');
      }
    });
  };

  const handleSettingsClick = (e) => {
    handleNavClick('/settings', e);
    // Also toggle the submenu
    const button = e.currentTarget;
    const navItem = button.closest('ag-sidebar-nav-item');
    const submenu = navItem?.querySelector('ag-sidebar-nav-submenu');

    if (!submenu) return;

    const currentAriaExpanded = button.getAttribute('aria-expanded');
    const isCurrentlyExpanded = currentAriaExpanded === 'true';

    if (isCurrentlyExpanded) {
      button.setAttribute('aria-expanded', 'false');
      submenu.removeAttribute('open');
    } else {
      button.setAttribute('aria-expanded', 'true');
      submenu.setAttribute('open', '');
    }
  };

  return (
    <section>
      <div className="mbe4">
        <h2>Default Sidebar with Navigation</h2>
        <p>
          Demonstrates the sidebar with navigation items, submenus, and both expanded/collapsed states.
          Click the header toggle to collapse into rail mode (icon-only).
        </p>
      </div>
      <div className="mbe6">
        <div style={{ position: 'relative', display: 'flex', height: '500px', border: '1px solid var(--ag-border-color)', borderRadius: '0.5rem', overflow: 'hidden', contain: 'layout' }}>
          <ReactSidebar
            open={sidebar1.isOpen}
            collapsed={sidebar1.isCollapsed}
            showMobileToggle={true}
            onToggle={(open) => setSidebar1({ ...sidebar1, isOpen: open })}
            onCollapse={(collapsed) => setSidebar1({ ...sidebar1, isCollapsed: collapsed })}
          >
            <h2
              slot="ag-header-start"
              style={{ margin: 0, fontSize: '1.125rem', fontWeight: 600 }}
            >
              Dashboard
            </h2>
            <button
              type="button"
              slot="ag-header-toggle"
              onClick={toggleSidebar1Collapse}
              style={{ background: 'none', border: 'none', padding: '8px 0', cursor: 'pointer', display: 'flex', alignItems: 'center', color: 'inherit' }}
              aria-label="Toggle sidebar"
            >
              <PanelIcon />
            </button>

            <ReactSidebarNav>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button active"
                  aria-current="page"
                >
                  <HomeIcon />
                  <span className="nav-label">Dashboard</span>
                </button>
              </ReactSidebarNavItem>

              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button nav-button-expanded"
                  aria-expanded="false"
                >
                  <FolderIcon />
                  <span className="nav-label">Projects</span>
                  <span className="chevron"><ChevronRightIcon /></span>
                </button>

                {/* Popover for COLLAPSED mode */}
                <ReactPopover
                  className="nav-button-collapsed"
                  placement="right-start"
                  triggerType="click"
                  distance={8}
                  arrow={true}
                >
                  <button
                    slot="trigger"
                    type="button"
                    className="nav-button"
                  >
                    <FolderIcon />
                    <span className="nav-label">Projects</span>
                    <span className="collapsed-indicator">
                      <svg viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M2 3l2 2 2-2" stroke="currentColor" strokeWidth="1" strokeLinecap="round"/>
                      </svg>
                    </span>
                  </button>
                  <ReactSidebarNavPopoverSubmenu
                    slot="content"
                    className="popover-submenu"
                  >
                    <a href="#" className="nav-sublink" onClick={(e) => e.preventDefault()}>Project Alpha</a>
                    <a href="#" className="nav-sublink" onClick={(e) => e.preventDefault()}>Project Beta</a>
                    <a href="#" className="nav-sublink" onClick={(e) => e.preventDefault()}>Project Gamma</a>
                  </ReactSidebarNavPopoverSubmenu>
                </ReactPopover>

                {/* Inline submenu for expanded mode */}
                <ReactSidebarNavSubmenu>
                  <a className="nav-sublink" href="#" onClick={(e) => e.preventDefault()}>Project Alpha</a>
                  <a className="nav-sublink" href="#" onClick={(e) => e.preventDefault()}>Project Beta</a>
                  <a className="nav-sublink" href="#" onClick={(e) => e.preventDefault()}>Project Gamma</a>
                </ReactSidebarNavSubmenu>
              </ReactSidebarNavItem>

              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                >
                  <UserIcon />
                  <span className="nav-label">Team</span>
                </button>
              </ReactSidebarNavItem>

              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button nav-button-expanded"
                  aria-expanded="false"
                >
                  <SettingsIcon />
                  <span className="nav-label">Settings</span>
                  <span className="chevron"><ChevronRightIcon /></span>
                </button>

                {/* Popover for COLLAPSED mode */}
                <ReactPopover
                  className="nav-button-collapsed"
                  placement="right-start"
                  triggerType="click"
                  distance={8}
                  arrow={true}
                >
                  <button
                    slot="trigger"
                    type="button"
                    className="nav-button"
                  >
                    <SettingsIcon />
                    <span className="nav-label">Settings</span>
                    <span className="collapsed-indicator">
                      <svg viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M2 3l2 2 2-2" stroke="currentColor" strokeWidth="1" strokeLinecap="round"/>
                      </svg>
                    </span>
                  </button>
                  <ReactSidebarNavPopoverSubmenu
                    slot="content"
                    className="popover-submenu"
                  >
                    <a href="#" className="nav-sublink" onClick={(e) => e.preventDefault()}>Profile</a>
                    <a href="#" className="nav-sublink" onClick={(e) => e.preventDefault()}>Billing</a>
                    <a href="#" className="nav-sublink" onClick={(e) => e.preventDefault()}>Security</a>
                    <a href="#" className="nav-sublink" onClick={(e) => e.preventDefault()}>Preferences</a>
                  </ReactSidebarNavPopoverSubmenu>
                </ReactPopover>

                <ReactSidebarNavSubmenu>
                  <a className="nav-sublink" href="#" onClick={(e) => e.preventDefault()}>Profile</a>
                  <a className="nav-sublink" href="#" onClick={(e) => e.preventDefault()}>Billing</a>
                  <a className="nav-sublink" href="#" onClick={(e) => e.preventDefault()}>Security</a>
                  <a className="nav-sublink" href="#" onClick={(e) => e.preventDefault()}>Preferences</a>
                </ReactSidebarNavSubmenu>
              </ReactSidebarNavItem>
            </ReactSidebarNav>

            <div
              slot="ag-footer"
              style={{ fontSize: '0.875rem', color: 'var(--ag-text-secondary)' }}
            >
              © 2024 Company
            </div>
          </ReactSidebar>

          <main style={{ flex: 1, padding: '2rem', overflow: 'auto', background: 'var(--ag-background)' }}>
            <h1 style={{ marginTop: 0 }}>Main Content</h1>
            <p>Click the header toggle to collapse the sidebar into rail mode.</p>
            <p>When collapsed, hover over items with submenus to see them in popovers.</p>
          </main>
        </div>
      </div>

      <div className="mbe4">
        <h2>Sidebar with Header Actions</h2>
        <p>
          Demonstrates using <code>ag-header-start</code>, <code>ag-header-end</code>, and <code>ag-header-toggle</code> slots
          for a composable header layout with action buttons.
        </p>
      </div>
      <div className="mbe6">
        <div style={{ position: 'relative', display: 'flex', height: '500px', border: '1px solid var(--ag-border-color)', borderRadius: '0.5rem', overflow: 'hidden', contain: 'layout' }}>
          <ReactSidebar
            open={sidebar2.isOpen}
            collapsed={sidebar2.isCollapsed}
            showMobileToggle={true}
            onToggle={(open) => setSidebar2({ ...sidebar2, isOpen: open })}
            onCollapse={(collapsed) => setSidebar2({ ...sidebar2, isCollapsed: collapsed })}
          >
            <h2
              slot="ag-header-start"
              style={{ margin: 0, fontSize: '1.125rem', fontWeight: 600 }}
            >
              My Application
            </h2>
            <button
              type="button"
              slot="ag-header-end"
              onClick={() => {}}
              style={{ background: 'none', border: 'none', padding: '8px', cursor: 'pointer', display: 'flex', alignItems: 'center', color: 'inherit', borderRadius: '0.25rem' }}
              aria-label="Settings"
              title="Settings"
            >
              <SettingsIcon />
            </button>
            <button
              type="button"
              slot="ag-header-toggle"
              onClick={toggleSidebar2Collapse}
              style={{ background: 'none', border: 'none', padding: '8px 0', cursor: 'pointer', display: 'flex', alignItems: 'center', color: 'inherit' }}
              aria-label="Toggle sidebar"
            >
              <PanelIcon />
            </button>

            <ReactSidebarNav>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button active"
                  aria-current="page"
                >
                  <HomeIcon />
                  <span className="nav-label">Dashboard</span>
                </button>
              </ReactSidebarNavItem>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                >
                  <FolderIcon />
                  <span className="nav-label">Projects</span>
                </button>
              </ReactSidebarNavItem>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                >
                  <UserIcon />
                  <span className="nav-label">Team</span>
                </button>
              </ReactSidebarNavItem>
            </ReactSidebarNav>

            <div
              slot="ag-footer"
              style={{ fontSize: '0.875rem', color: 'var(--ag-text-secondary)' }}
            >
              © 2024 Company
            </div>
          </ReactSidebar>

          <main style={{ flex: 1, padding: '2rem', overflow: 'auto', background: 'var(--ag-background)' }}>
            <h1 style={{ marginTop: 0 }}>Header Actions Pattern</h1>
            <p>The header includes a settings button in the <code>ag-header-end</code> slot.</p>
            <p>This allows for flexible header layouts with multiple action buttons.</p>
          </main>
        </div>
      </div>

      <div className="mbe4">
        <h2>Sidebar with Built-in Toggle</h2>
        <p>
          Using <code>show-header-toggle</code> adds a built-in collapse button automatically.
        </p>
      </div>
      <div className="mbe6">
        <div style={{ position: 'relative', display: 'flex', height: '500px', border: '1px solid var(--ag-border-color)', borderRadius: '0.5rem', overflow: 'hidden', contain: 'layout' }}>
          <ReactSidebar
            open={sidebar3.isOpen}
            collapsed={sidebar3.isCollapsed}
            showHeaderToggle={true}
            showMobileToggle={true}
            onToggle={(open) => setSidebar3({ ...sidebar3, isOpen: open })}
            onCollapse={(collapsed) => setSidebar3({ ...sidebar3, isCollapsed: collapsed })}
          >
            <h2
              slot="ag-header-start"
              style={{ margin: 0, fontSize: '1.125rem', fontWeight: 600 }}
            >
              Built-in Toggle
            </h2>

            <ReactSidebarNav>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button active"
                  aria-current="page"
                >
                  <HomeIcon />
                  <span className="nav-label">Dashboard</span>
                </button>
              </ReactSidebarNavItem>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                >
                  <FolderIcon />
                  <span className="nav-label">Projects</span>
                </button>
              </ReactSidebarNavItem>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                >
                  <UserIcon />
                  <span className="nav-label">Team</span>
                </button>
              </ReactSidebarNavItem>
            </ReactSidebarNav>

            <div
              slot="ag-footer"
              style={{ fontSize: '0.875rem', color: 'var(--ag-text-secondary)' }}
            >
              © 2024 Company
            </div>
          </ReactSidebar>

          <main style={{ flex: 1, padding: '2rem', overflow: 'auto', background: 'var(--ag-background)' }}>
            <h1 style={{ marginTop: 0 }}>Built-in Header Toggle</h1>
            <p>No need to provide a custom toggle button—the sidebar includes one automatically.</p>
          </main>
        </div>
      </div>

      <div className="mbe4">
        <h2>Disable Compact Mode</h2>
        <p>
          With <code>disable-compact-mode</code>, the sidebar has no intermediate collapsed/rail state.
          It's either fully open (expanded) or completely hidden. This pattern is used in applications like Claude AI Studio.
        </p>
      </div>
      <div className="mbe6">
        <div style={{ position: 'relative', display: 'flex', height: '500px', border: '1px solid var(--ag-border-color)', borderRadius: '0.5rem', overflow: 'hidden', contain: 'layout' }}>
          <ReactSidebar
            open={sidebar4.isOpen}
            disableCompactMode={true}
            showMobileToggle={true}
            onToggle={(open) => setSidebar4({ ...sidebar4, isOpen: open })}
          >
            <h2
              slot="ag-header-start"
              style={{ margin: 0, fontSize: '1.125rem', fontWeight: 600 }}
            >
              AI Studio
            </h2>
            <button
              type="button"
              slot="ag-header-toggle"
              onClick={toggleSidebar4Responsive}
              style={{ background: 'none', border: 'none', padding: '8px 0', cursor: 'pointer', display: 'flex', alignItems: 'center', color: 'inherit' }}
              aria-label="Toggle sidebar"
            >
              <CommandIcon />
            </button>

            <ReactSidebarNav>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button active"
                  aria-current="page"
                >
                  <HomeIcon />
                  <span className="nav-label">Dashboard</span>
                </button>
              </ReactSidebarNavItem>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                >
                  <FolderIcon />
                  <span className="nav-label">Projects</span>
                </button>
              </ReactSidebarNavItem>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                >
                  <UserIcon />
                  <span className="nav-label">Team</span>
                </button>
              </ReactSidebarNavItem>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                >
                  <SettingsIcon />
                  <span className="nav-label">Settings</span>
                </button>
              </ReactSidebarNavItem>
            </ReactSidebarNav>

            <div
              slot="ag-footer"
              style={{ fontSize: '0.875rem', color: 'var(--ag-text-secondary)' }}
            >
              © 2024 Company
            </div>
          </ReactSidebar>

          <main style={{ flex: 1, padding: '2rem', overflow: 'auto', background: 'var(--ag-background)' }}>
            <h1 style={{ marginTop: 0 }}>Disable Compact Mode</h1>
            <p>Use the mobile toggle button to show/hide the sidebar.</p>
            <p>Notice there's no collapsed/rail mode—it's either fully visible or completely hidden.</p>
          </main>
        </div>
      </div>

      <div className="mbe4">
        <h2>Active Item Tracking</h2>
        <p>
          Click navigation items to see the active state change. This demonstrates how to track the current route
          and apply active styling to both top-level and submenu items.
        </p>
      </div>
      <div className="mbe6">
        <div style={{ position: 'relative', display: 'flex', height: '500px', border: '1px solid var(--ag-border-color)', borderRadius: '0.5rem', overflow: 'hidden', contain: 'layout' }}>
          <ReactSidebar
            open={sidebar5.isOpen}
            collapsed={sidebar5.isCollapsed}
            showMobileToggle={true}
            onToggle={(open) => setSidebar5({ ...sidebar5, isOpen: open })}
            onCollapse={(collapsed) => setSidebar5({ ...sidebar5, isCollapsed: collapsed })}
          >
            <h2
              slot="ag-header-start"
              style={{ margin: 0, fontSize: '1.125rem', fontWeight: 600 }}
            >
              Navigation
            </h2>
            <button
              type="button"
              slot="ag-header-toggle"
              onClick={toggleSidebar5Collapse}
              style={{ background: 'none', border: 'none', padding: '8px 0', cursor: 'pointer', display: 'flex', alignItems: 'center', color: 'inherit' }}
              aria-label="Toggle sidebar"
            >
              <PanelIcon />
            </button>

            <ReactSidebarNav>
              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button active"
                  aria-current="page"
                  data-route="/dashboard"
                  onClick={(e) => handleNavClick('/dashboard', e)}
                >
                  <HomeIcon />
                  <span className="nav-label">Dashboard</span>
                </button>
              </ReactSidebarNavItem>

              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                  data-route="/projects"
                  onClick={(e) => handleNavClick('/projects', e)}
                >
                  <FolderIcon />
                  <span className="nav-label">Projects</span>
                </button>
              </ReactSidebarNavItem>

              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button"
                  data-route="/team"
                  onClick={(e) => handleNavClick('/team', e)}
                >
                  <UserIcon />
                  <span className="nav-label">Team</span>
                </button>
              </ReactSidebarNavItem>

              <ReactSidebarNavItem>
                <button
                  type="button"
                  className="nav-button nav-button-expanded"
                  aria-expanded="false"
                  data-route="/settings"
                  onClick={handleSettingsClick}
                >
                  <SettingsIcon />
                  <span className="nav-label">Settings</span>
                  <span className="chevron"><ChevronRightIcon /></span>
                </button>

                <ReactPopover
                  className="nav-button-collapsed"
                  placement="right-start"
                  triggerType="click"
                  distance={8}
                  arrow={true}
                >
                  <button
                    slot="trigger"
                    type="button"
                    className="nav-button"
                    data-route="/settings"
                    onClick={(e) => handleNavClick('/settings', e)}
                  >
                    <SettingsIcon />
                    <span className="nav-label">Settings</span>
                    <span className="collapsed-indicator">
                      <svg viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M2 3l2 2 2-2" stroke="currentColor" strokeWidth="1" strokeLinecap="round"/>
                      </svg>
                    </span>
                  </button>
                  <ReactSidebarNavPopoverSubmenu
                    slot="content"
                    className="popover-submenu"
                  >
                    <a
                      href="#"
                      className="nav-sublink"
                      data-route="/settings/profile"
                      onClick={(e) => handleNavClick('/settings/profile', e)}
                    >Profile</a>
                    <a
                      href="#"
                      className="nav-sublink"
                      data-route="/settings/billing"
                      onClick={(e) => handleNavClick('/settings/billing', e)}
                    >Billing</a>
                    <a
                      href="#"
                      className="nav-sublink"
                      data-route="/settings/security"
                      onClick={(e) => handleNavClick('/settings/security', e)}
                    >Security</a>
                  </ReactSidebarNavPopoverSubmenu>
                </ReactPopover>

                <ReactSidebarNavSubmenu>
                  <a
                    className="nav-sublink"
                    href="#"
                    data-route="/settings/profile"
                    onClick={(e) => handleNavClick('/settings/profile', e)}
                  >Profile</a>
                  <a
                    className="nav-sublink"
                    href="#"
                    data-route="/settings/billing"
                    onClick={(e) => handleNavClick('/settings/billing', e)}
                  >Billing</a>
                  <a
                    className="nav-sublink"
                    href="#"
                    data-route="/settings/security"
                    onClick={(e) => handleNavClick('/settings/security', e)}
                  >Security</a>
                </ReactSidebarNavSubmenu>
              </ReactSidebarNavItem>
            </ReactSidebarNav>
          </ReactSidebar>

          <main style={{ flex: 1, padding: '2rem', overflow: 'auto', background: 'var(--ag-background)' }}>
            <h1 style={{ marginTop: 0 }}>Active Item Tracking</h1>
            <p>Current route: <strong>{sidebar5.activeRoute}</strong></p>
            <p>Click navigation items to see the active state change.</p>
            <ul>
              <li><strong>Active styling:</strong> Background color and font weight change</li>
              <li><strong>ARIA current:</strong> The active item has aria-current="page" for accessibility</li>
              <li><strong>Submenu support:</strong> Sublinks also track active state</li>
              <li><strong>Popover sync:</strong> Active state works in both inline and popover modes</li>
            </ul>
            <p>
              In a real application, you'd integrate this with your router (Vue Router, etc.) to automatically
              update the active state based on the current route.
            </p>
          </main>
        </div>
      </div>
    </section>
  );
}
Open in StackBlitz

Usage

TIP

The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add Sidebar

The CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.

Vue
vue
<template>
  <div style="display: flex; height: 100vh;">
    <VueSidebar
      :open="isOpen"
      :collapsed="isCollapsed"
      :show-mobile-toggle="true"
      @update:open="isOpen = $event"
      @update:collapsed="isCollapsed = $event"
    >
      <h2
        slot="ag-header-start"
        style="margin: 0; font-size: 1.125rem; font-weight: 600;"
      >
        Dashboard
      </h2>
      <button
        type="button"
        slot="ag-header-toggle"
        @click="toggleCollapse"
        style="background: none; border: none; padding: 8px 0; cursor: pointer;"
        aria-label="Toggle sidebar"
      >
        <PanelIcon />
      </button>

      <VueSidebarNav>
        <VueSidebarNavItem>
          <button type="button" class="nav-button active" aria-current="page">
            <Home :size="20" />
            <span class="nav-label">Dashboard</span>
          </button>
        </VueSidebarNavItem>

        <VueSidebarNavItem>
          <!-- Button for expanded mode with rotating chevron -->
          <button
            type="button"
            class="nav-button nav-button-expanded"
            aria-expanded="false"
            @click="handleSubmenuToggle"
          >
            <Folder :size="20" />
            <span class="nav-label">Projects</span>
            <span class="chevron"><ChevronRight :size="16" /></span>
          </button>

          <!-- Popover for collapsed mode - shows when sidebar is collapsed -->
          <VuePopover
            class="nav-button-collapsed"
            placement="right-start"
            trigger-type="click"
            :distance="8"
            :arrow="true"
          >
            <button
              slot="trigger"
              type="button"
              class="nav-button"
              aria-expanded="false"
            >
              <Folder :size="20" />
              <span class="nav-label">Projects</span>
              <span class="collapsed-indicator">
                <svg
                  viewBox="0 0 8 8"
                  fill="none"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path
                    d="M2 3l2 2 2-2"
                    stroke="currentColor"
                    stroke-width="1"
                    stroke-linecap="round"
                  />
                </svg>
              </span>
            </button>
            <VueSidebarNavPopoverSubmenu
              slot="content"
              class="nav-popover-submenu"
            >
              <a href="#" class="nav-sublink">Project Alpha</a>
              <a href="#" class="nav-sublink">Project Beta</a>
            </VueSidebarNavPopoverSubmenu>
          </VuePopover>

          <!-- Inline submenu for expanded mode -->
          <VueSidebarNavSubmenu>
            <a class="nav-sublink" href="#">Project Alpha</a>
            <a class="nav-sublink" href="#">Project Beta</a>
          </VueSidebarNavSubmenu>
        </VueSidebarNavItem>
      </VueSidebarNav>

      <div slot="ag-footer" style="font-size: 0.875rem; color: #6b7280;">
        © 2024 Company
      </div>
    </VueSidebar>

    <main style="flex: 1; padding: 2rem;">
      <h1>Main Content</h1>
    </main>
  </div>
</template>

<script>
import VueSidebar from "agnosticui-core/sidebar/vue";
import {
  VueSidebarNav,
  VueSidebarNavItem,
  VueSidebarNavSubmenu,
  VueSidebarNavPopoverSubmenu,
} from "agnosticui-core/sidebar-nav/vue";
import { VuePopover } from "agnosticui-core/popover/vue";
import { Home, Folder, ChevronRight } from "lucide-vue-next";

export default {
  components: {
    VueSidebar,
    VueSidebarNav,
    VueSidebarNavItem,
    VueSidebarNavSubmenu,
    VueSidebarNavPopoverSubmenu,
    VuePopover,
    Home,
    Folder,
    ChevronRight,
  },
  data() {
    return {
      isOpen: true,
      isCollapsed: false,
    };
  },
  methods: {
    toggleCollapse() {
      // Access the sidebar element to call toggleCollapse
      const sidebar = this.$el.querySelector("ag-sidebar");
      if (sidebar && sidebar.toggleCollapse) {
        sidebar.toggleCollapse();
      }
    },
    handleSubmenuToggle(e) {
      const button = e.currentTarget;
      const navItem = button.closest("ag-sidebar-nav-item");
      const submenu = navItem?.querySelector("ag-sidebar-nav-submenu");

      if (!submenu) return;

      const isExpanded = button.getAttribute("aria-expanded") === "true";

      if (isExpanded) {
        button.setAttribute("aria-expanded", "false");
        submenu.removeAttribute("open");
      } else {
        button.setAttribute("aria-expanded", "true");
        submenu.setAttribute("open", "");
      }
    },
  },
};
</script>
React
tsx
import { useState } from "react";
import { ReactSidebar } from "agnosticui-core/sidebar/react";
import {
  ReactSidebarNav,
  ReactSidebarNavItem,
  ReactSidebarNavSubmenu,
  ReactSidebarNavPopoverSubmenu,
} from "agnosticui-core/sidebar-nav/react";
import { ReactPopover } from "agnosticui-core/popover/react";
import { Home, Folder, ChevronRight } from "lucide-react";

export default function SidebarExample() {
  const [isOpen, setIsOpen] = useState(true);
  const [isCollapsed, setIsCollapsed] = useState(false);

  const handleSubmenuToggle = (e) => {
    const button = e.currentTarget;
    const navItem = button.closest("ag-sidebar-nav-item");
    const submenu = navItem?.querySelector("ag-sidebar-nav-submenu");

    if (!submenu) return;

    const isExpanded = button.getAttribute("aria-expanded") === "true";

    if (isExpanded) {
      button.setAttribute("aria-expanded", "false");
      submenu.removeAttribute("open");
    } else {
      button.setAttribute("aria-expanded", "true");
      submenu.setAttribute("open", "");
    }
  };

  return (
    <div style={{ display: "flex", height: "100vh" }}>
      <ReactSidebar
        open={isOpen}
        collapsed={isCollapsed}
        showMobileToggle={true}
        onToggle={(open) => setIsOpen(open)}
        onCollapse={(collapsed) => setIsCollapsed(collapsed)}
      >
        <h2
          slot="ag-header-start"
          style={{ margin: 0, fontSize: "1.125rem", fontWeight: 600 }}
        >
          Dashboard
        </h2>

        <ReactSidebarNav>
          <ReactSidebarNavItem>
            <button
              type="button"
              className="nav-button active"
              aria-current="page"
            >
              <Home size={20} />
              <span className="nav-label">Dashboard</span>
            </button>
          </ReactSidebarNavItem>

          <ReactSidebarNavItem>
            {/* Button for expanded mode with rotating chevron */}
            <button
              type="button"
              className="nav-button nav-button-expanded"
              aria-expanded="false"
              onClick={handleSubmenuToggle}
            >
              <Folder size={20} />
              <span className="nav-label">Projects</span>
              <span className="chevron">
                <ChevronRight size={16} />
              </span>
            </button>

            {/* Popover for collapsed mode - shows when sidebar is collapsed */}
            <ReactPopover
              className="nav-button-collapsed"
              placement="right-start"
              triggerType="click"
              distance={8}
              arrow={true}
            >
              <button
                slot="trigger"
                type="button"
                className="nav-button"
                aria-expanded="false"
              >
                <Folder size={20} />
                <span className="nav-label">Projects</span>
                <span className="collapsed-indicator">
                  <svg
                    viewBox="0 0 8 8"
                    fill="none"
                    xmlns="http://www.w3.org/2000/svg"
                  >
                    <path
                      d="M2 3l2 2 2-2"
                      stroke="currentColor"
                      strokeWidth="1"
                      strokeLinecap="round"
                    />
                  </svg>
                </span>
              </button>
              <ReactSidebarNavPopoverSubmenu
                slot="content"
                className="nav-popover-submenu"
              >
                <a href="#project-alpha" className="nav-sublink">
                  Project Alpha
                </a>
                <a href="#project-beta" className="nav-sublink">
                  Project Beta
                </a>
              </ReactSidebarNavPopoverSubmenu>
            </ReactPopover>

            {/* Inline submenu for expanded mode */}
            <ReactSidebarNavSubmenu>
              <a className="nav-sublink" href="#">
                Project Alpha
              </a>
              <a className="nav-sublink" href="#">
                Project Beta
              </a>
            </ReactSidebarNavSubmenu>
          </ReactSidebarNavItem>
        </ReactSidebarNav>

        <div
          slot="ag-footer"
          style={{ fontSize: "0.875rem", color: "var(--ag-text-secondary)" }}
        >
          © 2024 Company
        </div>
      </ReactSidebar>

      <main style={{ flex: 1, padding: "2rem" }}>
        <h1>Main Content</h1>
      </main>
    </div>
  );
}
Lit (Web Components)
typescript
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
import "agnosticui-core/sidebar";
import "agnosticui-core/sidebar-nav";
import "agnosticui-core/popover";

@customElement("sidebar-example")
export class SidebarExample extends LitElement {
  static styles = css`
    :host {
      display: block;
    }
  `;

  firstUpdated() {
    // Set up event listeners for submenu toggles in the shadow DOM
    const buttons = this.shadowRoot?.querySelectorAll(".nav-button-expanded");
    buttons?.forEach((button) => {
      button.addEventListener("click", (e: Event) => {
        const navItem = (e.currentTarget as HTMLElement).closest(
          "ag-sidebar-nav-item"
        );
        const submenu = navItem?.querySelector("ag-sidebar-nav-submenu");

        if (!submenu) return;

        const isExpanded = button.getAttribute("aria-expanded") === "true";

        if (isExpanded) {
          button.setAttribute("aria-expanded", "false");
          submenu.removeAttribute("open");
        } else {
          button.setAttribute("aria-expanded", "true");
          submenu.setAttribute("open", "");
        }
      });
    });
  }

  render() {
    return html`
      <div style="display: flex; height: 100vh;">
        <ag-sidebar id="sidebar" show-mobile-toggle>
          <h2
            slot="ag-header-start"
            style="margin: 0; font-size: 1.125rem; font-weight: 600;"
          >
            Dashboard
          </h2>

          <ag-sidebar-nav>
            <ag-sidebar-nav-item>
              <button
                type="button"
                class="nav-button active"
                aria-current="page"
              >
                <svg><!-- Home icon --></svg>
                <span class="nav-label">Dashboard</span>
              </button>
            </ag-sidebar-nav-item>

            <ag-sidebar-nav-item>
              <!-- Button for expanded mode with rotating chevron -->
              <button
                type="button"
                class="nav-button nav-button-expanded"
                aria-expanded="false"
              >
                <svg><!-- Folder icon --></svg>
                <span class="nav-label">Projects</span>
                <span class="chevron"><!-- ChevronRight icon --></span>
              </button>

              <!-- Popover for collapsed mode - shows when sidebar is collapsed -->
              <ag-popover
                class="nav-button-collapsed"
                placement="right-start"
                trigger-type="click"
                distance="8"
                arrow
              >
                <button
                  slot="trigger"
                  type="button"
                  class="nav-button"
                  aria-expanded="false"
                >
                  <svg><!-- Folder icon --></svg>
                  <span class="nav-label">Projects</span>
                  <span class="collapsed-indicator">
                    <svg
                      viewBox="0 0 8 8"
                      fill="none"
                      xmlns="http://www.w3.org/2000/svg"
                    >
                      <path
                        d="M2 3l2 2 2-2"
                        stroke="currentColor"
                        stroke-width="1"
                        stroke-linecap="round"
                      />
                    </svg>
                  </span>
                </button>
                <ag-sidebar-nav-popover-submenu
                  slot="content"
                  class="nav-popover-submenu"
                >
                  <a class="nav-sublink" href="#">Project Alpha</a>
                  <a class="nav-sublink" href="#">Project Beta</a>
                </ag-sidebar-nav-popover-submenu>
              </ag-popover>

              <!-- Inline submenu for expanded mode -->
              <ag-sidebar-nav-submenu>
                <a class="nav-sublink" href="#">Project Alpha</a>
                <a class="nav-sublink" href="#">Project Beta</a>
              </ag-sidebar-nav-submenu>
            </ag-sidebar-nav-item>
          </ag-sidebar-nav>

          <div
            slot="ag-footer"
            style="font-size: 0.875rem; color: var(--ag-text-secondary);"
          >
            © 2024 Company
          </div>
        </ag-sidebar>

        <main style="flex: 1; padding: 2rem;">
          <h1>Main Content</h1>
        </main>
      </div>
    `;
  }
}

Note: When using sidebar components within a custom element's shadow DOM, set up event listeners in the component's lifecycle (e.g., firstUpdated()) rather than using DOMContentLoaded, as document.querySelector() cannot access elements inside shadow DOM. Use this.shadowRoot.querySelector() instead.

Props

PropTypeDefaultDescription
openbooleanfalseControls sidebar visibility on mobile
collapsedbooleanfalseControls collapsed/rail state (icon-only) on desktop
position'left' | 'right''left'Position of the sidebar
ariaLabelstring'Navigation'ARIA label for the sidebar
variant'default' | 'bordered' | 'elevated''default'Visual variant of the sidebar
noTransitionbooleanfalseDisable transitions
widthstring-Custom width for the sidebar (e.g., '300px')
disableCompactModebooleanfalseDisable compact/rail mode (sidebar is either full-width or hidden)
showMobileTogglebooleanfalseShow the mobile toggle button
mobileTogglePosition'top-left' | 'top-right' | 'bottom-left' | 'bottom-right''top-left'Position of the mobile toggle button
showHeaderTogglebooleanfalseShow the built-in header toggle button

The Sidebar uses companion components for navigation:

  • VueSidebarNav / ReactSidebarNav / ag-sidebar-nav - Container for navigation items
  • VueSidebarNavItem / ReactSidebarNavItem / ag-sidebar-nav-item - Individual navigation item wrapper
  • VueSidebarNavSubmenu / ReactSidebarNavSubmenu / ag-sidebar-nav-submenu - Inline submenu for expanded mode
  • VueSidebarNavPopoverSubmenu / ReactSidebarNavPopoverSubmenu / ag-sidebar-nav-popover-submenu - Popover submenu for collapsed mode

Events

EventPayloadDescription
@update:open (Vue) / onToggle (React) / toggle (Lit)booleanEmitted when the sidebar open state changes
@update:collapsed (Vue) / onCollapse (React) / collapse (Lit)booleanEmitted when the sidebar collapsed state changes

Slots

Vue

  • slot="ag-header-start": Left-aligned header content (logo or title)
  • slot="ag-header-end": Right-aligned header content (actions, before toggle)
  • slot="ag-header-toggle": Custom collapse toggle button (always rightmost)
  • slot="ag-header": Monolithic header slot (legacy, for full custom header)
  • Default slot: Main content area for navigation
  • slot="ag-footer": Footer content

React

  • slot="ag-header-start": Left-aligned header content
  • slot="ag-header-end": Right-aligned header content
  • slot="ag-header-toggle": Custom collapse toggle button
  • slot="ag-header": Monolithic header slot (legacy)
  • children: Main content area for navigation
  • slot="ag-footer": Footer content

Lit

  • slot="ag-header-start": Left-aligned header content
  • slot="ag-header-end": Right-aligned header content
  • slot="ag-header-toggle": Custom collapse toggle button
  • slot="ag-header": Monolithic header slot (legacy)
  • Default slot: Main content area for navigation
  • slot="ag-footer": Footer content

Methods

MethodDescription
toggleCollapse()Toggles the collapsed state of the sidebar
toggleResponsive()Toggles the responsive open/close state (used with disableCompactMode)

Accessibility

The Sidebar component follows accessibility best practices:

  • Uses semantic HTML with proper ARIA attributes
  • Implements role="navigation" with customizable aria-label
  • Supports keyboard navigation with Tab and Shift+Tab
  • Active navigation items use aria-current="page" for screen readers
  • Submenu buttons use aria-expanded to indicate state
  • Focus management for smooth keyboard navigation
  • Mobile overlay includes backdrop for visual separation
  • Toggle buttons have proper aria-label attributes
  • Collapsed state indicators are handled with appropriate ARIA attributes

Best Practices

  • Always provide an ariaLabel to identify the sidebar navigation purpose
  • Use aria-current="page" on the active navigation item
  • Ensure submenu buttons have aria-expanded attribute
  • Provide clear visual indicators for active states
  • Use the .nav-label class on text content that should hide in collapsed mode
  • Structure navigation buttons with flexbox for proper icon/label layout
  • Include both inline and popover versions of submenus for responsive behavior
  • Test keyboard navigation flow through all items
  • Ensure mobile toggle is easily accessible (consider thumb zones)

Use Cases

Expanded/Collapsed Mode (Default)

The sidebar can toggle between full-width (expanded) and icon-only (rail/collapsed) modes on desktop. On mobile, it becomes an overlay that can be opened/closed.

Use for:

  • Dashboard applications with complex navigation
  • Admin panels with multiple sections
  • Documentation sites with nested navigation

Disable Compact Mode

With disableCompactMode, the sidebar has no intermediate collapsed state—it's either fully open or completely hidden. This matches patterns like Claude AI Studio.

Use for:

  • Applications where icon-only mode isn't needed
  • Simpler navigation structures
  • Mobile-first applications

Header Patterns

Composable Header (ag-header-start, ag-header-end, ag-header-toggle):

  • Logo/title on the left
  • Actions (settings, notifications) in the middle
  • Toggle button on the right

Monolithic Header (ag-header):

  • Full control over header layout
  • Logo-as-toggle pattern
  • Custom header interactions

Built-in Toggle (show-header-toggle):

  • Automatic toggle button
  • No custom header slot needed
  • Consistent behavior

Simple Navigation: Single-level menu with icons and labels

Nested Navigation: Submenus that expand inline when sidebar is expanded, and show in popovers when collapsed

Active Item Tracking: Visual indication of current page with styling and aria-current

Styling Navigation

The Sidebar component provides the structure, but navigation button styling is typically handled in your application CSS (global light DOM styles). Here's the complete CSS required for a fully functional sidebar with all features:

css
/* CRITICAL: Sidebar component width - must be defined! */
ag-sidebar {
  /* Expanded width */
  width: 260px;
  transition: width 0.3s ease;
  overflow: visible; /* Allow popovers to show outside */
}

ag-sidebar[collapsed] {
  /* Collapsed width - icon only */
  width: 64px;
}

/* Navigation button base styles */
.nav-button {
  display: flex;
  align-items: center;
  gap: var(--ag-space-3);
  position: relative; /* Required for collapsed-indicator positioning */
  padding: var(--ag-space-2) var(--ag-space-3);
  border: none;
  background: none;
  cursor: pointer;
  width: 100%;
  text-align: left;
  border-radius: var(--ag-radius-md);
  transition: all var(--ag-fx-duration-sm);
  color: var(--ag-text-primary);
  font-size: var(--ag-font-size-base);
}

.nav-button:hover {
  background: var(--ag-background-secondary);
}

.nav-button.active {
  background: var(--ag-primary);
  color: white;
  font-weight: 500;
}

.nav-label {
  flex-grow: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: opacity var(--ag-fx-duration-sm);
}

/* Chevron rotation for expanded submenus */
.chevron {
  display: flex;
  align-items: center;
  transition: transform var(--ag-fx-duration-md), opacity var(--ag-fx-duration-sm);
  margin-left: auto;
}

.nav-button[aria-expanded="true"] .chevron {
  transform: rotate(90deg);
}

/* Collapsed indicator - small triangle at 4:30 position */
.collapsed-indicator {
  display: none;
  position: absolute;
  bottom: -1px;
  right: -1px;
  width: var(--ag-space-3);
  height: var(--ag-space-3);
}

.collapsed-indicator svg {
  color: var(--ag-text-secondary);
  transform: rotate(315deg);
}

/* Show collapsed indicator in collapsed mode for buttons with submenus */
ag-sidebar[collapsed] .nav-button[aria-expanded] .collapsed-indicator {
  display: block;
}

/* CRITICAL: Properly handle collapsed state */
ag-sidebar[collapsed] .nav-label,
ag-sidebar[collapsed] .chevron {
  opacity: 0;
  width: 0;
  overflow: hidden;
}

/* Center icons in collapsed mode */
ag-sidebar[collapsed] .nav-button {
  justify-content: center;
  padding: var(--ag-space-2);
  gap: 0;
}

/* Visibility rules for expanded vs collapsed mode */
ag-sidebar[collapsed] ag-sidebar-nav-submenu:not(.popover-submenu),
ag-sidebar:not([collapsed]) ag-popover,
ag-sidebar[collapsed] .nav-button-expanded,
ag-sidebar:not([collapsed]) .nav-button-collapsed {
  display: none !important;
}

/* Fix popover centering in collapsed mode */
ag-sidebar[collapsed] ag-popover.nav-button-collapsed {
  display: block !important;
}

/* CRITICAL: Submenu visibility - hidden by default, visible when open */
ag-sidebar-nav-submenu {
  display: none;
  overflow: hidden;
}

ag-sidebar-nav-submenu[open] {
  display: block;
}

/* Submenu link styles */
.nav-sublink {
  display: block;
  padding: var(--ag-space-2) var(--ag-space-3);
  padding-left: var(--ag-space-8);
  color: var(--ag-text-secondary);
  text-decoration: none;
  border-radius: var(--ag-radius-md);
  transition: background var(--ag-fx-duration-sm);
}

.nav-sublink:hover {
  background: var(--ag-background-secondary);
  color: var(--ag-text-primary);
}

/* Popover submenu styles */
.nav-popover-submenu .nav-sublink {
  padding-left: var(--ag-space-3);
}

/* Popover content padding for nav popovers in collapsed mode */
.nav-button-collapsed::part(ag-popover-body) {
  padding: var(--ag-space-1);
}

/* Hide popover header for nav popovers */
.nav-button-collapsed::part(ag-popover-header) {
  display: none;
}

Collapsed Indicator Markup

When using popovers for collapsed mode submenus, add the collapsed indicator inside the trigger button:

html
<button slot="trigger" type="button" class="nav-button" aria-expanded="false">
  <FolderIcon />
  <span class="nav-label">Projects</span>
  <span class="collapsed-indicator">
    <svg viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path
        d="M2 3l2 2 2-2"
        stroke="currentColor"
        stroke-width="1"
        stroke-linecap="round"
      />
    </svg>
  </span>
</button>

This small triangular indicator appears at the bottom-right corner (4:30 clock position) when the sidebar is collapsed, helping users identify which navigation items have submenus.