Sidebar
import { Sidebar } from '@tangible/ui';
Interactive docs and examples: Sidebar in Storybook.
Sidebar props
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
Sidebar content (Header, Nav, etc.). | |
aria-label? |
string |
Accessible label for the sidebar landmark (static) or dialog (drawer). When a page has multiple sidebars, each must have a unique label so screen reader users can distinguish between landmarks (e.g. "Course navigation", "Lesson settings"). | |
className? |
string |
Additional CSS class names. | |
collapsed? |
boolean |
Collapsed state (controlled). Pair with onCollapsedChange. Leave undefined to let the sidebar own the state. |
|
collapsible? |
boolean |
false |
Opt into the collapsed rail: the sidebar can shrink to --tui-sidebar-collapsed-width, hiding Header, Nav and Footer while leaving any other child — a Sidebar.Toggle, an icon rail — visible. Deliberately absent from drawer mode: a drawer is already a show/hide overlay, and a rail inside one has nothing to sit beside. |
defaultCollapsed? |
boolean |
false |
Initial collapsed state when uncontrolled. |
drawer? |
boolean |
false |
Enable drawer mode (mobile overlay). |
onClose? |
() => void |
Callback to close the drawer. Required when drawer={true}. Called when ESC is pressed or backdrop is clicked. Omitting this in drawer mode means the user cannot dismiss the overlay, which is a WCAG 2.1.1 (Keyboard) failure. |
|
onCollapsedChange? |
(collapsed: boolean) => void |
Called with the next collapsed state whenever Sidebar.Toggle is pressed — in both controlled and uncontrolled mode. |
|
open? |
boolean |
Drawer open state. | |
position? |
"left" | "right" |
'left' |
Sidebar position. Affects border placement and drawer slide direction. |
Sidebar.Header
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
Header content (back button, title, progress, CTAs). | |
className? |
string |
Additional CSS class names. |
Sidebar.Footer
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
Footer content (CTAs, user info, etc.). Sticks to the bottom of the sidebar. | |
className? |
string |
Additional CSS class names. |
Sidebar.Nav
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
Nav content (typically Accordion with StepList). | |
aria-label? |
string |
Accessible label for the nav landmark. Use this for the primary label since nav is the actionable region. | |
aria-labelledby? |
string |
ID of a visible heading that labels the navigation. | |
className? |
string |
Additional CSS class names. |
Sidebar.Toggle
| Prop | Type | Default | Description |
|---|---|---|---|
className? |
string |
Additional CSS class names. | |
disabled? |
boolean |
Disabled state. | |
href? |
never |
||
labels? |
SidebarToggleLabels |
Accessible names for the two states. Defaults are English and are exported as defaultSidebarToggleLabels; a partial object overrides only the keys it names. |
|
loading? |
boolean |
Loading state: shows a spinner and blocks activation while keeping the button focusable (aria-disabled, not native disabled), so a keyboard user who triggered it keeps their place. Only onClick is guarded: put the action there, not in onKeyDown/onPointerDown. The start and end of loading aren't announced; confirm completion through your own status message (a Toast, say). |
|
loadingLabel? |
string |
`${label}, loading` (English) |
Accessible label override during loading state. For i18n support. |
shape? |
"circle" | "square" |
'square' |
Button shape. Use 'circle' for avatar-adjacent actions, FABs, or anywhere a round affordance is expected. |
showTooltip? |
boolean |
false |
Show the label as a tooltip on hover. Since icon-only buttons have no visible text, enabling this helps sighted users discover what the button does. Recommended for most standalone icon buttons. Can be omitted in toolbars where a shared Tooltip provider handles it, or where adjacent visible text already communicates the action. |
size? |
"lg" | "md" | "sm" | "xs" |
'sm' |
Size of the button. 'xs': 24px — below WCAG 2.5.8 minimum target size (24×24 CSS px). Use only in dense, mouse-first UIs (toolbars, table rows) where touch is not expected. Consider pairing with adequate spacing or a larger touch target via padding/margin. 'sm': 32px (default); 'md': 40px; 'lg': 48px |
theme? |
"danger" | "info" | "primary" | "secondary" | "success" | "warning" |
'secondary' |
Semantic color theme. Note: Defaults to 'secondary' (unlike Button which defaults to 'primary') because IconButtons are typically used for toolbar/utility actions. |
tooltipSide? |
"bottom" | "left" | "right" | "top" |
'top' |
Tooltip placement side. |
variant? |
"ghost" | "outline" | "solid" |
'ghost' |
Visual style variant. 'solid': Filled background; 'outline': Border only, transparent background; 'ghost': No border or background, icon only — has no at-rest visual affordance. Best suited for toolbar groups, icon bars, or other contexts where surrounding UI makes the interactive nature obvious. |
Also accepts the standard attributes of ButtonHTMLAttributes (data-*, aria-*, event handlers), passed to the element it renders.
Other exports
defaultSidebarToggleLabels:Required<SidebarToggleLabels>
Examples
-
Collapsible:
collapsiblelets the sidebar shrink to a rail. Header, Nav and Footer go with it; anything else placed directly in the sidebar stays — which is whySidebar.Togglebelongs there and not inside the Header. -
Collapsed Rail: Starts as a rail. The toggle is the only thing left to bring it back.
-
Controlled Collapse: Controlled: the parent owns
collapsedand the sidebar only reports intent throughonCollapsedChange. Worth a story of its own because a consumer that forgets to write the value back gets a toggle that announces a state change and then does nothing — the sidebar deliberately will not move itself. -
Collapsible (Right): On a right-hand sidebar the glyph mirrors: both directions collapse away from the content.
-
Drawer Open Interaction: Drawer opens with focus inside, role="dialog", and aria-modal.
-
Drawer Escape Interaction: Escape closes the drawer and restores focus to the trigger.
-
Drawer Backdrop Interaction: Clicking the backdrop closes the drawer.
-
Drawer Focus Trap Interaction: Focus trap cycles Tab within the drawer.
-
Drawer Scroll Lock Interaction: Body scroll lock class is applied while the drawer is open.
-
Collapse Focus Recovery Interaction: Regression cover for the focus hole in a programmatic collapse: when
collapsedflips from outside while focus sits in a region the rail hides, the browser drops focus to<body>and the user's place in the page is gone silently. Clicking the toggle never hits this — focus is already on the one element that survives — so only an external control reproduces it.This has to live in a browser. jsdom has no layout, so
offsetParentis null for every element and the guard that separates "hidden under the cursor" from "user clicked away" cannot be exercised there at all. -
Collapse Interaction: The toggle survives its own collapse and keeps the sidebar reachable from the keyboard — the regions it hides leave the tab order with it.
Other stories: Static, With Header and Nav Slots, With Footer, Right Position, Drawer Mode, Drawer (Right Position), Drawer Keyboard Interaction, Course Viewer (Full Integration) (interactive page).
Component API tokens
Read via fallback, never declared. Set one on an ancestor to restyle every instance under it.
--tui-sidebar-width--tui-sidebar-collapsed-width--tui-sidebar-bg--tui-sidebar-fg--tui-sidebar-border--tui-sidebar-divider--tui-sidebar-padding--tui-sidebar-z