Sidebar

June 7, 2026 · View on GitHub

App side navigation container. Expanded (240px) or collapsed (64px), data-driven sections of NavItems, optional search slot, user footer with Avatar.

Contract

<kp-sidebar> renders a vertical nav rail. Content is data-driven via [sections] — each section has an optional label and a list of KpSidebarNavItem rows.

The sidebar is self-controlled: clicking the toggle mutates widthState internally and emits toggle with the new value for persistence. If the consumer drives widthState as an Input, subsequent changes override the internal state.

In collapsed + showLogo=true state, the logo is visible by default and the toggle fades in on hover (they share the same slot). In all other states, both elements stay where they are.

API

Inputs

NameTypeDefaultDescription
widthState'expanded' | 'collapsed''expanded'240px vs 64px
appearance'light' | 'dark''light'Surface color
showLogobooleantrueHide when the app Header already renders the brand (e.g. inside AppShell)
logoTextstring'Kanso Protocol'
showSearchbooleanfalseReveal the search slot
showSectionLabelsbooleantrueUppercase labels above each section
showUserFooterbooleantrueBottom user block — hide when Header already has a user menu
sectionsKpSidebarSection[][]Data-driven sections
userName, userEmail, userInitialsstring | nullnullFooter user data

Outputs

NamePayloadFires when
toggleKpSidebarWidth ('expanded' | 'collapsed')Toggle clicked — payload is the new state
itemClickKpSidebarNavItemAny nav item clicked
userMenuClickvoidFooter menu dots clicked

Projection slots

SelectorSlot
[kpSidebarLogo]Replace default logo mark + text
[kpSidebarSearch]Search input above sections

KpSidebarNavItem

interface KpSidebarNavItem {
  label: string;
  /** Tabler icon name without the `ti-` prefix, e.g. `'layout-dashboard'` */
  icon?: string;
  href?: string;
  active?: boolean;
  badge?: string;
  children?: KpSidebarNavItem[];
  expanded?: boolean;
}

Widths

StateWidthHides
expanded240
collapsed64Labels, badges, chevrons, section headers, footer text

Do / Don't

Do

  • Persist widthState in localStorage. Users expect the sidebar to stay where they left it.
  • Group actions into 2–3 sections max. Long undifferentiated rails are hard to scan; prefer a dense first section + smaller footer section.
  • Use the "active" flag on exactly one item at a time. Let the consumer compute it from the current route.

Don't

  • Don't mix navigation and CTAs in sidebar items. A "Upgrade" CTA belongs in the Header, not as a NavItem.
  • Don't nest more than 2 levels. Side rails get unreadable fast.
  • Don't hide the collapse toggle — discoverability matters. Keep it reachable in both states.

Accessibility

  • role="navigation" on the host.
  • Collapse toggle announces its state via aria-label.
  • Active item inherits aria-current="page" from the NavItem.

References

Changelog

  • 0.1.1 — Toggle button is interactive (mutates widthState + emits new value); in collapsed + showLogo=true the toggle swaps in on hover; showLogo and showUserFooter can now be disabled when the Header already carries them (AppShell composition).
  • 0.1.0 — Initial release. 2 widths × 2 appearances, data-driven sections, user footer with Avatar.