App shell

Sidebar

App navigation: header, sections of links, a keyboard-navigable page tree, footer and bottom bar. Inside an AppFrame it docks, floats when collapsed, and becomes the phone drawer.

Usage

  • Use it as the sidebar of an AppFrame for app-level navigation between pages and sections.
  • Use href items for navigation and button items (onClick) for actions such as “New page”; mark the current page active.
  • Use Sidebar.Tree for nested, user-created pages; use Sidebar.Section + Sidebar.Item for a fixed menu.
  • Don’t put forms or long content in the sidebar; keep rows to one line (labels truncate).
  • Don’t use it for in-page navigation (a table of contents); use PageTocRail or Tabs.

Installation

Install the package (React 19):

terminal
npm install @krackedui/motion

Import the styles once, in your app entry or root layout:

/src/main.tsx
import '@krackedui/motion/styles.css';

Use it anywhere inside your app's <Theme> (see Installation):

/src/SidebarExample.tsx
import { Sidebar, WorkspaceMenu, HelpMenu, AppsMenu, Icon } from '@krackedui/motion';

export function SidebarExample() {
  return (
    <div className="h-[600px]">
      <Sidebar.Root
        header={<><Sidebar.Search /><Sidebar.Tabs /></>}
        footer={<Sidebar.InviteCard />}
        bottomBar={
          <Sidebar.BottomBar>
            <WorkspaceMenu email="ada@example.com" workspaces={[{ id: 'w1', name: 'Design Team' }]} currentId="w1">
              <Sidebar.WorkspaceSwitcher name="Design Team" />
            </WorkspaceMenu>
            <HelpMenu releases={[{ id: 'r1', title: 'Release 3.8', isNew: true }, { id: 'r2', title: 'Release 3.7' }]}>
              <Sidebar.BarButton icon="questionMarkCircle" label="Help, contact, more…" dot />
            </HelpMenu>
            <AppsMenu trash={[{ id: 't1', title: 'Old meeting notes', path: 'Team wiki' }]}>
              <Sidebar.BarButton icon="circleGrid2X2" label="Library, Trash, more…" />
            </AppsMenu>
          </Sidebar.BottomBar>
        }
      >
        <Sidebar.Group>
          <Sidebar.Item icon="chatBubblePlus" actions={<Sidebar.Action icon="ellipsisSmall" label="Open shortcut menu" />}>New chat</Sidebar.Item>
          <Sidebar.Item icon="bookshelf">Library</Sidebar.Item>
        </Sidebar.Group>
        <Sidebar.Sections>
          <Sidebar.Section title="Shared" actions={<Sidebar.Action icon="plusSmall" label="Add a page" />}>
            <Sidebar.Item tone="secondary" icon={<Icon name="plus" size={16} />}>Start collaborating</Sidebar.Item>
          </Sidebar.Section>
          <Sidebar.Section title="Private" actions={<Sidebar.Action icon="plusSmall" label="Add a page" />}>
            <Sidebar.Tree label="Private pages">
              <Sidebar.TreeItem label="Team wiki" active defaultExpanded>
                <Sidebar.TreeItem label="Tasks" icon={<Icon name="viewTable" size={20} />} />
              </Sidebar.TreeItem>
              <Sidebar.TreeItem label="Q4 roadmap" />
              <Sidebar.TreeItem label="Meeting notes" />
              <Sidebar.TreeItem label="Product specs" />
            </Sidebar.Tree>
          </Sidebar.Section>
        </Sidebar.Sections>
        <Sidebar.ScrollFooter>
          <Sidebar.Item tone="muted" icon="sliders">Customize sidebar</Sidebar.Item>
        </Sidebar.ScrollFooter>
      </Sidebar.Root>
    </div>
  );
}

Examples

The contract parts: Header, Section, Item (href, active, trailing) and Footer.

Page tree

Hover actions are opt-in: "+" with onAdd, "…" with onMore (or moreSlot); rename them for other content with addLabel / moreLabel.

Team wiki
Product specs
No pages inside

Item tones

Bottom bar

Composition

anatomy
<Sidebar.Root header footer bottomBar>      // nav "Sidebar", 270 wide, resizable 200–480
  <Sidebar.Header/>  pinned top (or the header prop: <Sidebar.Search/> <Sidebar.Tabs/>)
  scroller   → <Sidebar.Section title actions>
                 <Sidebar.Item icon href active trailing actions/>
               </Sidebar.Section>
               <Sidebar.Tree> <Sidebar.TreeItem> <Sidebar.TreeItem/> </Sidebar.TreeItem> </Sidebar.Tree>
               <Sidebar.ScrollFooter> <Sidebar.Item/> </Sidebar.ScrollFooter>
  <Sidebar.Footer/>  pinned bottom (or footer / bottomBar props)
</Sidebar.Root>

API Reference

Based on <nav>. Adds:

PropTypeDefault
width
number
270
onWidthChange
(width: number) => void
minWidth
number
200
maxWidth
number
480
header
ReactNode
footer
ReactNode
bottomBar
ReactNode
floating
boolean
onCollapse
() => void
label
string
"Sidebar"
children
ReactNode

Based on <div>. Pinned top region (does not scroll).

PropTypeDefault
...props
HTMLAttributes<HTMLDivElement>

Based on <div>. Pinned bottom region with a top hairline.

PropTypeDefault
...props
HTMLAttributes<HTMLDivElement>

Based on <button>.

PropTypeDefault
label
string
"Search or ask"
shortcut
string
"⌘K"

Based on <div> (except onChange).

PropTypeDefault
tabs
SidebarTab[]
Home, Chat, Meetings
value
string
defaultValue
string
first tab
onValueChange
(id: string) => void
trailing
ReactNode
« collapse button
onCollapse
() => void

ShellGlyph

The Home / Chat / Meetings glyphs used by Sidebar.Tabs (an Icon; other SVG props pass through). Use it to draw the same glyph elsewhere, e.g. a custom tab trigger.

PropTypeDefault
name
'home' | 'chat' | 'calendar'
size
number
20

A row whose control is <a> (with href), <button>, or your element (asChild). Other props go to the control.

PropTypeDefault
children
ReactNode
icon
IconName | string | ReactElement
iconSize
number
22
href
string
asChild
boolean
false
onClick
(e: MouseEvent) => void
active
boolean
false
trailing
ReactNode
tone
"default""muted""secondary"
"default"
actions
ReactNode
rowClassName
string

Based on <button>. 20x20 hover affordance.

PropTypeDefault
icon
IconName
label
string

Based on <div> (except title).

PropTypeDefault
title
ReactNode
actions
ReactNode
defaultOpen
boolean
true
open
boolean
onOpenChange
(open: boolean) => void

Based on <div role="tree">.

PropTypeDefault
label
string

Based on <div> (except children / onSelect).

PropTypeDefault
label
ReactNode
icon
IconName | ReactElement
"page"
emoji
string
active
boolean
false
defaultExpanded
boolean
false
expanded
boolean
onExpandedChange
(expanded: boolean) => void
children
ReactNode
emptyLabel
ReactNode
"No pages inside"
onSelect
() => void
onAdd
() => void
addLabel
string
"Add a page inside"
onMore
(e: MouseEvent<HTMLButtonElement>) => void
moreLabel
string
"Delete, duplicate, and more…"
moreSlot
ReactNode

Based on <button> (except title), plus a dismiss button.

PropTypeDefault
title
ReactNode
"Invite members"
description
ReactNode
"Collaborate with your team."
icon
IconName
"inviteMember"
onDismiss
() => void

Based on <button>.

PropTypeDefault
name
string
avatar
ReactNode
letter avatar

Based on <button>. 28x28.

PropTypeDefault
icon
IconName
label
string
dot
boolean
false

Layout wrappers based on <div>: rows with 1px gap, sections with 4px gap, bottom-of-scroller footer, 44px bottom bar.

PropTypeDefault
...props
HTMLAttributes<HTMLDivElement>

Accessibility

  • nav landmark labelled "Sidebar" (label). Items are real links (href) or buttons; the current page has aria-current="page".
  • Sections are labelled groups; the heading is a button with aria-expanded that collapses the content (animated height).
  • The page tree follows the WAI-ARIA tree pattern: one tab stop; ↑/↓ move, → expands or moves to the first child, ← collapses or moves to the parent, Home/End jump, Enter opens. Items expose aria-level, aria-expanded, aria-selected.
  • Hover actions (+, …) of the focused row are reachable with Tab and appear on focus; on touch screens they are always visible and targets grow to 24px+.
  • The resize handle is a "Sidebar width" slider (a native range input announcing the width): →/↑ widen, ←/↓ narrow by 10px, Page Up/Down by 50px, Home/End jump to minWidth/maxWidth; clicking the strip collapses the sidebar.
  • The tabs are a tablist (←/→, Home/End move and select).
© 2026 Motion. Internal documentation.