Tessina UI
Tessina UI
GitHubIntroduction
InstallationUsageTheming
Components
ButtonIconButtonLinkSpinnerBadgeAvatarStatusChipShortcutSkeletonSurfaceProgressMeterRating
LabelFieldFieldsetCheckboxRadioSwitchSliderSelectComboboxSearchTextareaNumberFieldDate PickerTime PickerOTP InputFile UploadCalendar
AlertBannerToastTooltip
TabsAccordionCollapsibleBreadcrumbPaginationStepperSegmentedControlButtonGroupToggleButtonToggleGroupToolbarNavigation MenuMenubarBottom NavSidebar
Split ButtonFABDropdown MenuContextMenuCommandPopoverHoverCard
ContainerStackFlexGridAspectRatioSpacerCardCarouselDividerScroll Area
Table
ChatBubblePromptInputCodeBlock
ModalAlertDialogDrawerAction SheetTop Header MobileTop Header DesktopEmptyStateForm
Contributing
ComponentsNavigation Blocks

Sidebar

A composable, accessible sidebar shell for app layouts. Desktop and mobile modes, three variants (inset, floating, classic), collapsible to icon rail or off-canvas, keyboard shortcut toggle, LTR and RTL. Built on Base UI Collapsible with a full compound-component API.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import {
  SidebarProvider,
  Sidebar,
  SidebarHeader,
  SidebarContent,
  SidebarFooter,
  SidebarBrand,
  SidebarSearch,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarGroupContent,
  SidebarMenu,
  SidebarMenuItem,
  SidebarMenuButton,
  SidebarMenuBadge,
  SidebarMenuAction,
  SidebarMenuSub,
  SidebarMenuSubItem,
  SidebarMenuSubButton,
  SidebarTrigger,
  SidebarInset,
  SidebarSeparator,
  useSidebar,
} from "@tessinaui/ui";
<SidebarProvider>
  <Sidebar>
    <SidebarHeader>
      <SidebarBrand name="Workspace" initial="W" />
      <SidebarSearch placeholder="Search…" />
    </SidebarHeader>

    <SidebarContent>
      <SidebarGroup>
        <SidebarGroupLabel>Navigation</SidebarGroupLabel>
        <SidebarGroupContent>
          <SidebarMenu>
            <SidebarMenuItem>
              <SidebarMenuButton active>
                <Home className="size-4" />
                <span>Home</span>
              </SidebarMenuButton>
            </SidebarMenuItem>
            <SidebarMenuItem>
              <SidebarMenuButton>
                <Inbox className="size-4" />
                <span>Inbox</span>
                <SidebarMenuBadge>12</SidebarMenuBadge>
              </SidebarMenuButton>
              <SidebarMenuAction aria-label="More">
                <MoreHorizontal className="size-3.5" />
              </SidebarMenuAction>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarGroupContent>
      </SidebarGroup>
    </SidebarContent>

    <SidebarFooter>
      {/* footer actions */}
    </SidebarFooter>
  </Sidebar>

  <SidebarInset>
    {/* page content */}
  </SidebarInset>
</SidebarProvider>

The provider sets CSS custom properties for sidebar width and tracks open/collapsed state across the tree. Wrap any layout in SidebarProvider; sibling Sidebar and SidebarInset build a two-column app shell.

Showcase

When to use

  • Dashboard shells: primary navigation on the side of a content area — projects, inboxes, settings apps
  • Tool palettes: persistent lists of destinations, grouped and searchable
  • Editorial apps: folders, collections, pinned items with counts and submenus

If you just need a temporary overlay panel, reach for Drawer. If you need a top bar for a small-screen app, use TopHeaderMobile with a Drawer.

API Reference

SidebarProvider

Root state container. Provides sidebar context (open, openMobile, isMobile, size, dir) to the entire subtree. Also sets CSS variables (--sidebar-width, --sidebar-width-icon, --sidebar-width-mobile) on its wrapper.

PropTypeDefaultDescription
defaultOpenbooleantrueInitial open state on desktop
openboolean—Controlled open state
onOpenChange(open: boolean) => void—Called when open state changes
size"sm" | "md" | "lg""md"Controls width of expanded rail
dir"ltr" | "rtl""ltr"Text and layout direction
styleCSSProperties—Merged with the provider's own CSS variables

Also accepts any <div> attribute (className, id, etc.).

Keyboard shortcut: Cmd/Ctrl + B toggles the sidebar — on desktop it flips open, on mobile it flips openMobile.

Sidebar

The rail itself. Renders as <aside> on desktop and as a slide-in panel on mobile (via an internal backdrop + translate-x animation — no Dialog focus trap, so the sidebar stays in the page render tree).

PropTypeDefaultDescription
variant"sidebar" | "floating" | "inset""sidebar"Visual treatment. floating detaches the panel with margin + rounded border; inset pushes content content inward inside a shared surface
collapsible"offcanvas" | "icon" | "none""offcanvas"What "closed" means. icon collapses to a rail showing only icons; offcanvas hides the sidebar; none pins it open
side"left" | "right""left"Which edge the sidebar sits on. Automatically flipped under dir="rtl"
intent"default" | "inverse""default"default uses bg-background / text-foreground; inverse uses bg-foreground / text-background for a Noir-style dark rail

State data attributes (for CSS targeting):

  • data-state="open" \| "collapsed"
  • data-collapsible="icon" \| "offcanvas" \| "none"
  • data-variant="sidebar" \| "floating" \| "inset"
  • data-side="left" \| "right"
  • data-intent="default" \| "inverse"
  • data-mobile (present when rendering the mobile slide-in)

SidebarTrigger

Button that toggles the sidebar. Uses the appropriate handler based on isMobile. Accepts any <button> attribute and an onClick that runs alongside the toggle.

SidebarRail

Thin hit-area on the inner edge of the sidebar. Clicking it toggles open/collapsed. Useful when collapsible="icon" — gives the user an always-visible toggle target.

SidebarInset

Wrapper for page content that sits next to the sidebar. On desktop, reserves space using flexbox. On variant="inset", applies the shared surface border/radius.

SidebarHeader / SidebarFooter

Layout slots pinned to the top and bottom of the rail. Padded by default. Accept any <div> attribute.

SidebarBrand

Branded identity row. Shows an avatar tile with initial and a name/meta pair. Collapses gracefully to just the tile in icon mode.

PropTypeDefaultDescription
nameReactNode—Primary label (workspace name)
metaReactNode—Secondary label (plan, handle)
initialstring"A"One-letter avatar tile text
avatarReactNode—Replace the initial tile with a custom node
actionReactNode—Trailing node — commonly a chevron

SidebarSearch

Pre-styled search field. Icon on the start, optional shortcut hint on the end. Hidden when the rail is collapsed to icons. Accepts all <input> attributes plus:

PropTypeDefaultDescription
shortcutstring—Right-side shortcut hint (e.g. ⌘K)

SidebarContent

Main scroll region between header and footer. Accepts any <div> attribute.

SidebarSeparator

Thin horizontal divider. Padded on the sides to align with menu items.

SidebarGroup

Logical section. Can be collapsible.

PropTypeDefaultDescription
collapsiblebooleanfalseWrap the group in Base UI Collapsible so the label acts as a trigger
defaultOpenbooleantrueInitial open state of the collapsible group

SidebarGroupLabel

Section caption. If the group is collapsible, renders as a Collapsible.Trigger <button> with a chevron. Hidden when the rail is collapsed to icons.

SidebarGroupAction

Small icon button at the end of the label row (e.g. a + action). Hidden when the rail is collapsed to icons.

SidebarGroupContent

Wrapper for group children. When the parent SidebarGroup is collapsible, wraps in Collapsible.Panel.

SidebarMenu / SidebarMenuItem

List and list-item primitives. SidebarMenu renders <ul>, SidebarMenuItem renders <li> with position: relative so badges and actions can absolutely position.

SidebarMenuButton

The clickable row. Default renders as <button>; pass render={<a ...>…} to swap in an anchor or a Next.js <Link>.

PropTypeDefaultDescription
variant"default" | "ghost" | "pill""default"Hover/active treatment
size"sm" | "md" | "lg""md"Vertical density
activebooleanfalseApplies the active/selected styling
disabledbooleanfalseDims and blocks interaction
tooltipstring—Title attribute shown when collapsed to icons
renderReactElement—Swap the underlying element

Content: place an icon (<SomeIcon className="size-4" />) and a <span> label inside. The label is auto-hidden when the rail is collapsed to icons; the icon stays visible.

SidebarMenuAction

Small icon action that floats on the end of a menu row (e.g. three-dot "more" button). Renders as a <button>, so it must be a sibling of SidebarMenuButton inside SidebarMenuItem — never a child of the button (nested <button> elements are invalid HTML). Absolutely positioned against the menu item. Hidden when the rail is collapsed to icons.

<SidebarMenuItem>
  <SidebarMenuButton>…</SidebarMenuButton>
  <SidebarMenuAction aria-label="More">
    <MoreHorizontal className="size-3.5" />
  </SidebarMenuAction>
</SidebarMenuItem>

SidebarMenuBadge

Counter or status pill floated on the end of a menu row. Renders as a <span> and uses margin-inline-start: auto to push to the end of its flex container — so it belongs inside SidebarMenuButton (which is the flex row). Accepts any <span> attribute plus:

PropTypeDefaultDescription
tone"neutral" | "primary" | "success" | "warning" | "error" | "info""neutral"Maps to Tessina intent tokens (bg-*-light / text-*)
<SidebarMenuItem>
  <SidebarMenuButton>
    <Inbox className="size-4" />
    <span>Inbox</span>
    <SidebarMenuBadge>12</SidebarMenuBadge>
  </SidebarMenuButton>
</SidebarMenuItem>

Hidden when the rail is collapsed to icons.

SidebarMenuSub / SidebarMenuSubItem / SidebarMenuSubButton

Nested tree of links under a parent SidebarMenuItem. SidebarMenuSub renders <ul> with a start border to hint the tree; SidebarMenuSubButton behaves like SidebarMenuButton but slightly denser and without icon slot rules.

useSidebar()

React hook for consuming the sidebar context.

const { open, setOpen, openMobile, setOpenMobile, toggle, isMobile, size, dir } = useSidebar();

Throws if called outside a SidebarProvider.

Variants

VariantWhen to use
sidebarClassic app shell — flush against the edge, shares a border with content
floatingDetached card look — sits on an off-white surface with a soft border and rounded corners
insetModern dashboard — sidebar and content share a unified surface with inner padding

Collapsing behaviour

collapsibleClosed state
offcanvasSlides fully out of view; toggle brings it back
iconShrinks to an icon rail (--sidebar-width-icon); labels hide, icons stay
noneAlways open — no toggle behaviour

RTL

Pass dir="rtl" on SidebarProvider. The sidebar flips sides automatically, chevrons and badge positions mirror, and logical CSS (border-s, ms-auto, ps-) keeps paddings consistent. All compound parts respect the direction via context.

Accessibility

  • Navigation is a semantic <nav> with aria-label="Sidebar".
  • Collapsible groups use Base UI Collapsible — the label is a real <button> with aria-expanded.
  • The rail target is a <button> with aria-label="Toggle Sidebar".
  • Mobile overlay is dismissible via backdrop click or the Escape key; focus returns to the trigger on close.
  • All menu buttons respond to Space / Enter and receive a visible focus ring (focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2).
  • Cmd/Ctrl + B toggles the sidebar globally.

Bottom Nav

Mobile bottom navigation bar in two styles — a full-width dock and an iOS-style floating pill. Controlled or uncontrolled active state. Badge support (dot or count). LTR and RTL.

Split Button

A compound button with a primary action and a dropdown trigger for secondary options

On this page

PlaygroundInstallationUsageShowcaseWhen to useAPI ReferenceSidebarProviderSidebarSidebarTriggerSidebarRailSidebarInsetSidebarHeader / SidebarFooterSidebarBrandSidebarSearchSidebarContentSidebarSeparatorSidebarGroupSidebarGroupLabelSidebarGroupActionSidebarGroupContentSidebarMenu / SidebarMenuItemSidebarMenuButtonSidebarMenuActionSidebarMenuBadgeSidebarMenuSub / SidebarMenuSubItem / SidebarMenuSubButtonuseSidebar()VariantsCollapsing behaviourRTLAccessibility