Sidebar
An application sidebar panel. Docked beside the page's main content it can be resized and collapsed; below its breakpoint it becomes an off-canvas drawer. It is only the panel: the page lays it out next to its content.
Workspace navigation
A compact sidebar with its own toggle for narrow screens.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-sidebar/ui-sidebar.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-nav-item/ui-nav-item.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon-button/ui-icon-button.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon/ui-icon.html"
>
<ui-sidebar
id="preview-sidebar"
width="176"
min-width="160"
breakpoint="sm"
aria-label="Workspace"
>
<ui-stack
gap="xs"
>
<ui-nav-item
href="#notes"
current="true"
>Notes</ui-nav-item>
<ui-nav-item
href="#files"
>Files</ui-nav-item>
</ui-stack>
</ui-sidebar>
<ui-icon-button
commandfor="preview-sidebar"
command="--toggle"
label="Toggle sidebar"
>
<ui-icon
name="panel-left"
></ui-icon>
</ui-icon-button><script setup>
import { UiIcon, UiIconButton, UiNavItem, UiSidebar, UiStack } from "@threadlabs/looma";
</script>
<template>
<UiSidebar
id="preview-sidebar"
:width="176"
:min-width="160"
breakpoint="sm"
aria-label="Workspace"
>
<UiStack
gap="xs"
>
<UiNavItem
href="#notes"
current="true"
>Notes</UiNavItem>
<UiNavItem
href="#files"
>Files</UiNavItem>
</UiStack>
</UiSidebar>
<UiIconButton
commandfor="preview-sidebar"
command="--toggle"
label="Toggle sidebar"
>
<UiIcon
name="panel-left"
/>
</UiIconButton>
</template>import { UiIcon, UiIconButton, UiNavItem, UiSidebar, UiStack } from "@threadlabs/looma";
export default function Example() {
return <>
<UiSidebar
id="preview-sidebar"
width={176}
minWidth={160}
breakpoint="sm"
aria-label="Workspace"
>
<UiStack
gap="xs"
>
<UiNavItem
href="#notes"
current="true"
>Notes</UiNavItem>
<UiNavItem
href="#files"
>Files</UiNavItem>
</UiStack>
</UiSidebar>
<UiIconButton
commandfor="preview-sidebar"
command="--toggle"
label="Toggle sidebar"
>
<UiIcon
name="panel-left"
/>
</UiIconButton>
</>;
}/** @jsxImportSource preact */
import { UiIcon, UiIconButton, UiNavItem, UiSidebar, UiStack } from "@threadlabs/looma";
export default function Example() {
return <>
<UiSidebar
id="preview-sidebar"
width={176}
minWidth={160}
breakpoint="sm"
aria-label="Workspace"
>
<UiStack
gap="xs"
>
<UiNavItem
href="#notes"
current="true"
>Notes</UiNavItem>
<UiNavItem
href="#files"
>Files</UiNavItem>
</UiStack>
</UiSidebar>
<UiIconButton
commandfor="preview-sidebar"
command="--toggle"
label="Toggle sidebar"
>
<UiIcon
name="panel-left"
/>
</UiIconButton>
</>;
}<script>
import { UiIcon, UiIconButton, UiNavItem, UiSidebar, UiStack } from "@threadlabs/looma";
</script>
<UiSidebar
id="preview-sidebar"
width={176}
minWidth={160}
breakpoint="sm"
aria-label="Workspace"
>
<UiStack
gap="xs"
>
<UiNavItem
href="#notes"
current="true"
>Notes</UiNavItem>
<UiNavItem
href="#files"
>Files</UiNavItem>
</UiStack>
</UiSidebar>
<UiIconButton
commandfor="preview-sidebar"
command="--toggle"
label="Toggle sidebar"
>
<UiIcon
name="panel-left"
/>
</UiIconButton>import { Component } from "@angular/core";
import { UiIcon, UiIconButton, UiNavItem, UiSidebar, UiStack } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSidebar, UiStack, UiNavItem, UiIconButton, UiIcon],
template: `
<aside
ui-sidebar
id="preview-sidebar"
[width]="176"
[minWidth]="160"
[breakpoint]="'sm'"
aria-label="Workspace"
>
<div
ui-stack
[gap]="'xs'"
>
<ng-template
ui-nav-item
[href]="'#notes'"
[current]="'true'"
>Notes</ng-template>
<ng-template
ui-nav-item
[href]="'#files'"
>Files</ng-template>
</div>
</aside>
<button
ui-icon-button
commandfor="preview-sidebar"
command="--toggle"
[label]="'Toggle sidebar'"
>
<span
ui-icon
name="panel-left"
></span>
</button>
`,
})
export default class Example {}App shell
The sidebar is the panel only; the page places it beside its main content. The button toggles it with commandfor and command="--toggle": docked it collapses, and below the breakpoint (40rem in this example) it opens as a drawer. Each open and close is one toggle event, whose detail gives open, mode, and trigger; as a drawer the popover's own toggle event, which has no detail, reaches the panel too.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-sidebar/ui-sidebar.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-nav-item/ui-nav-item.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon-button/ui-icon-button.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-card/ui-card.html"
>
<ui-cluster
wrap="nowrap"
align="stretch"
gap="m"
>
<ui-sidebar
breakpoint="sm"
id="sidebar-nav"
aria-label="Workspace"
>
<ui-stack
gap="s"
role="navigation"
aria-label="Office sections"
>
<ui-text
variant="eyebrow"
>Fellowship office</ui-text>
<ui-nav-item
as="a"
href="#inbox"
current="true"
>Inbox</ui-nav-item>
<ui-nav-item
as="a"
href="#projects"
>Quest planning</ui-nav-item>
<ui-nav-item
as="a"
href="#settings"
>Office settings</ui-nav-item>
</ui-stack>
</ui-sidebar>
<ui-stack
fill
role="main"
>
<ui-icon-button
commandfor="sidebar-nav"
command="--toggle"
label="Toggle sidebar"
>
<svg
aria-hidden="true"
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"
></rect>
<path
d="M9 3v18"
></path>
</svg>
</ui-icon-button>
<ui-card
padding="sm"
>Frodo and Sam’s team updates sit beside the office navigation.</ui-card>
</ui-stack>
</ui-cluster><script setup>
import {
UiCard,
UiCluster,
UiIconButton,
UiNavItem,
UiSidebar,
UiStack,
UiText,
} from "@threadlabs/looma";
</script>
<template>
<UiCluster
wrap="nowrap"
align="stretch"
gap="m"
>
<UiSidebar
breakpoint="sm"
id="sidebar-nav"
aria-label="Workspace"
>
<UiStack
gap="s"
role="navigation"
aria-label="Office sections"
>
<UiText
variant="eyebrow"
>Fellowship office</UiText>
<UiNavItem
as="a"
href="#inbox"
current="true"
>Inbox</UiNavItem>
<UiNavItem
as="a"
href="#projects"
>Quest planning</UiNavItem>
<UiNavItem
as="a"
href="#settings"
>Office settings</UiNavItem>
</UiStack>
</UiSidebar>
<UiStack
:fill="true"
role="main"
>
<UiIconButton
commandfor="sidebar-nav"
command="--toggle"
label="Toggle sidebar"
>
<svg
aria-hidden="true"
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"
></rect>
<path
d="M9 3v18"
></path>
</svg>
</UiIconButton>
<UiCard
padding="sm"
>Frodo and Sam’s team updates sit beside the office navigation.</UiCard>
</UiStack>
</UiCluster>
</template>import {
UiCard,
UiCluster,
UiIconButton,
UiNavItem,
UiSidebar,
UiStack,
UiText,
} from "@threadlabs/looma";
export default function Example() {
return <UiCluster
wrap="nowrap"
align="stretch"
gap="m"
>
<UiSidebar
breakpoint="sm"
id="sidebar-nav"
aria-label="Workspace"
>
<UiStack
gap="s"
role="navigation"
aria-label="Office sections"
>
<UiText
variant="eyebrow"
>Fellowship office</UiText>
<UiNavItem
as="a"
href="#inbox"
current="true"
>Inbox</UiNavItem>
<UiNavItem
as="a"
href="#projects"
>Quest planning</UiNavItem>
<UiNavItem
as="a"
href="#settings"
>Office settings</UiNavItem>
</UiStack>
</UiSidebar>
<UiStack
fill
role="main"
>
<UiIconButton
commandfor="sidebar-nav"
command="--toggle"
label="Toggle sidebar"
>
<svg
aria-hidden="true"
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>
</UiIconButton>
<UiCard
padding="sm"
>Frodo and Sam’s team updates sit beside the office navigation.</UiCard>
</UiStack>
</UiCluster>;
}/** @jsxImportSource preact */
import {
UiCard,
UiCluster,
UiIconButton,
UiNavItem,
UiSidebar,
UiStack,
UiText,
} from "@threadlabs/looma";
export default function Example() {
return <UiCluster
wrap="nowrap"
align="stretch"
gap="m"
>
<UiSidebar
breakpoint="sm"
id="sidebar-nav"
aria-label="Workspace"
>
<UiStack
gap="s"
role="navigation"
aria-label="Office sections"
>
<UiText
variant="eyebrow"
>Fellowship office</UiText>
<UiNavItem
as="a"
href="#inbox"
current="true"
>Inbox</UiNavItem>
<UiNavItem
as="a"
href="#projects"
>Quest planning</UiNavItem>
<UiNavItem
as="a"
href="#settings"
>Office settings</UiNavItem>
</UiStack>
</UiSidebar>
<UiStack
fill
role="main"
>
<UiIconButton
commandfor="sidebar-nav"
command="--toggle"
label="Toggle sidebar"
>
<svg
aria-hidden="true"
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>
</UiIconButton>
<UiCard
padding="sm"
>Frodo and Sam’s team updates sit beside the office navigation.</UiCard>
</UiStack>
</UiCluster>;
}<script>
import {
UiCard,
UiCluster,
UiIconButton,
UiNavItem,
UiSidebar,
UiStack,
UiText,
} from "@threadlabs/looma";
</script>
<UiCluster
wrap="nowrap"
align="stretch"
gap="m"
>
<UiSidebar
breakpoint="sm"
id="sidebar-nav"
aria-label="Workspace"
>
<UiStack
gap="s"
role="navigation"
aria-label="Office sections"
>
<UiText
variant="eyebrow"
>Fellowship office</UiText>
<UiNavItem
as="a"
href="#inbox"
current="true"
>Inbox</UiNavItem>
<UiNavItem
as="a"
href="#projects"
>Quest planning</UiNavItem>
<UiNavItem
as="a"
href="#settings"
>Office settings</UiNavItem>
</UiStack>
</UiSidebar>
<UiStack
fill
role="main"
>
<UiIconButton
commandfor="sidebar-nav"
command="--toggle"
label="Toggle sidebar"
>
<svg
aria-hidden="true"
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"
></rect>
<path
d="M9 3v18"
></path>
</svg>
</UiIconButton>
<UiCard
padding="sm"
>Frodo and Sam’s team updates sit beside the office navigation.</UiCard>
</UiStack>
</UiCluster>import { Component } from "@angular/core";
import {
UiCard,
UiCluster,
UiIconButton,
UiNavItem,
UiSidebar,
UiStack,
UiText,
} from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiSidebar, UiStack, UiText, UiNavItem, UiIconButton, UiCard],
template: `
<div
ui-cluster
[wrap]="'nowrap'"
[align]="'stretch'"
[gap]="'m'"
>
<aside
ui-sidebar
[breakpoint]="'sm'"
id="sidebar-nav"
aria-label="Workspace"
>
<div
ui-stack
[gap]="'s'"
role="navigation"
aria-label="Office sections"
>
<ng-template
ui-text
[variant]="'eyebrow'"
>Fellowship office</ng-template>
<ng-template
ui-nav-item
[as]="'a'"
[href]="'#inbox'"
[current]="'true'"
>Inbox</ng-template>
<ng-template
ui-nav-item
[as]="'a'"
[href]="'#projects'"
>Quest planning</ng-template>
<ng-template
ui-nav-item
[as]="'a'"
[href]="'#settings'"
>Office settings</ng-template>
</div>
</aside>
<div
ui-stack
[fill]="true"
role="main"
>
<button
ui-icon-button
commandfor="sidebar-nav"
command="--toggle"
[label]="'Toggle sidebar'"
>
<svg
aria-hidden="true"
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"
></rect>
<path
d="M9 3v18"
></path>
</svg>
</button>
<ng-template
ui-card
[padding]="'sm'"
>Frodo and Sam’s team updates sit beside the office navigation.</ng-template>
</div>
</div>
`,
})
export default class Example {}End side, resizable
side="end" puts the panel on the end edge. resizable adds a handle on its inner edge: drag it, or focus it and use the arrow keys, Home, and End, within min-width and max-width. Double-click resets the width.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-card/ui-card.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-sidebar/ui-sidebar.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<ui-cluster
wrap="nowrap"
align="stretch"
gap="m"
>
<ui-stack
fill
role="main"
>
<ui-card
padding="sm"
>The Fellowship office memo comes first, with its details alongside.</ui-card>
</ui-stack>
<ui-sidebar
breakpoint="sm"
side="end"
width="260"
resizable
min-width="200"
max-width="380"
aria-label="Details"
>
<ui-stack
gap="s"
>
<ui-text
variant="eyebrow"
>Memo details</ui-text>
<p>Drag the inner edge, or focus it and use the arrow keys.</p>
</ui-stack>
</ui-sidebar>
</ui-cluster><script setup>
import { UiCard, UiCluster, UiSidebar, UiStack, UiText } from "@threadlabs/looma";
</script>
<template>
<UiCluster
wrap="nowrap"
align="stretch"
gap="m"
>
<UiStack
:fill="true"
role="main"
>
<UiCard
padding="sm"
>The Fellowship office memo comes first, with its details alongside.</UiCard>
</UiStack>
<UiSidebar
breakpoint="sm"
side="end"
:width="260"
:resizable="true"
:min-width="200"
:max-width="380"
aria-label="Details"
>
<UiStack
gap="s"
>
<UiText
variant="eyebrow"
>Memo details</UiText>
<p>Drag the inner edge, or focus it and use the arrow keys.</p>
</UiStack>
</UiSidebar>
</UiCluster>
</template>import { UiCard, UiCluster, UiSidebar, UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
wrap="nowrap"
align="stretch"
gap="m"
>
<UiStack
fill
role="main"
>
<UiCard
padding="sm"
>The Fellowship office memo comes first, with its details alongside.</UiCard>
</UiStack>
<UiSidebar
breakpoint="sm"
side="end"
width={260}
resizable
minWidth={200}
maxWidth={380}
aria-label="Details"
>
<UiStack
gap="s"
>
<UiText
variant="eyebrow"
>Memo details</UiText>
<p>Drag the inner edge, or focus it and use the arrow keys.</p>
</UiStack>
</UiSidebar>
</UiCluster>;
}/** @jsxImportSource preact */
import { UiCard, UiCluster, UiSidebar, UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
wrap="nowrap"
align="stretch"
gap="m"
>
<UiStack
fill
role="main"
>
<UiCard
padding="sm"
>The Fellowship office memo comes first, with its details alongside.</UiCard>
</UiStack>
<UiSidebar
breakpoint="sm"
side="end"
width={260}
resizable
minWidth={200}
maxWidth={380}
aria-label="Details"
>
<UiStack
gap="s"
>
<UiText
variant="eyebrow"
>Memo details</UiText>
<p>Drag the inner edge, or focus it and use the arrow keys.</p>
</UiStack>
</UiSidebar>
</UiCluster>;
}<script>
import { UiCard, UiCluster, UiSidebar, UiStack, UiText } from "@threadlabs/looma";
</script>
<UiCluster
wrap="nowrap"
align="stretch"
gap="m"
>
<UiStack
fill
role="main"
>
<UiCard
padding="sm"
>The Fellowship office memo comes first, with its details alongside.</UiCard>
</UiStack>
<UiSidebar
breakpoint="sm"
side="end"
width={260}
resizable
minWidth={200}
maxWidth={380}
aria-label="Details"
>
<UiStack
gap="s"
>
<UiText
variant="eyebrow"
>Memo details</UiText>
<p>Drag the inner edge, or focus it and use the arrow keys.</p>
</UiStack>
</UiSidebar>
</UiCluster>import { Component } from "@angular/core";
import { UiCard, UiCluster, UiSidebar, UiStack, UiText } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiStack, UiCard, UiSidebar, UiText],
template: `
<div
ui-cluster
[wrap]="'nowrap'"
[align]="'stretch'"
[gap]="'m'"
>
<div
ui-stack
[fill]="true"
role="main"
>
<ng-template
ui-card
[padding]="'sm'"
>The Fellowship office memo comes first, with its details alongside.</ng-template>
</div>
<aside
ui-sidebar
[breakpoint]="'sm'"
[side]="'end'"
[width]="260"
[resizable]="true"
[minWidth]="200"
[maxWidth]="380"
aria-label="Details"
>
<div
ui-stack
[gap]="'s'"
>
<ng-template
ui-text
[variant]="'eyebrow'"
>Memo details</ng-template>
<p>Drag the inner edge, or focus it and use the arrow keys.</p>
</div>
</aside>
</div>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: aside
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
breakpoint | Below this viewport width (40rem, 48rem, 64rem) the sidebar becomes an off-canvas drawer, closed until toggled. | breakpoint | keyword | md | sm, md, lg |
collapsed | Whether the docked sidebar is collapsed. | collapsed | boolean | false | — |
max-width | The widest width the resize handle allows, in pixels. | maxWidth | number | 480 | — |
min-width | The narrowest width the resize handle allows, in pixels. | minWidth | number | 200 | — |
resizable | Adds a resize handle on the inner edge (drag, or arrow keys, Home, and End). | resizable | boolean | false | — |
resize-label | Accessible name of the resize handle. | resizeLabel | string | Resize sidebar | — |
resize-step | Arrow-key resize step in pixels. | resizeStep | number | 16 | — |
side | The edge the sidebar sits on. The page places it beside its main content. | side | keyword | start | start, end |
width | Docked width in pixels. 0 uses --ui-sidebar-width (280px by default); dragging the handle updates it. | width | number | 0 | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
breakpoint | "sm" | "md" | "lg" | md | sm, md, lg |
collapsed | boolean | false | — |
maxWidth | number | 480 | — |
minWidth | number | 200 | — |
resizable | boolean | false | — |
resizeLabel | string | Resize sidebar | — |
resizeStep | number | 16 | — |
side | "start" | "end" | start | start, end |
width | number | 0 | — |
Events
| Name | Detail | Description |
|---|---|---|
resize | object
| Emitted with the declared detail payload. |
toggle | object
| Emitted once each time the panel opens or closes, docked or as a drawer. |
Slots
| Name | Purpose |
|---|---|
default | Default child content. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
| Token | Declared values | Fallback |
|---|---|---|
--ui-sidebar-backdrop | — | rgb(0 0 0 / 0.32) |
--ui-sidebar-border | — | var(--ui-border-divider) |
--ui-sidebar-collapsed-width | — | 0px |
--ui-sidebar-surface | — | var(--ui-surface-default) |
--ui-sidebar-width | — | 280px |
Shared tokens
| Token | Fallback |
|---|---|
--ui-accent-solid | var(--ui-focus-ring) |
--ui-affordance-direct-color | var(--ui-accent-solid, var(--ui-focus-ring)) |
--ui-affordance-guide-color | var(--ui-border-strong) |
--ui-affordance-guide-opacity | — |
--ui-affordance-motion | var(--ui-motion-fast) |
--ui-border-divider | — |
--ui-border-strong | — |
--ui-border-width | — |
--ui-elevation-overlay | var(--ui-shadow-lg) |
--ui-focus-ring | — |
--ui-motion-ease | — |
--ui-motion-fast | — |
--ui-motion-layout | — |
--ui-motion-layout-ease | — |
--ui-motion-reveal | — |
--ui-motion-reveal-ease | — |
--ui-radius-round | — |
--ui-scroll-fade-size | 24px |
--ui-shadow-lg | — |
--ui-surface-default | — |
--ui-surface-elevated | — |