Westy Design Systemv0.2.7
OverviewComponentsExamplesStorybook

Foundations

  • Typography
  • Colors
  • Spacing
  • Radius
  • Shadows
  • Icons

Components

  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Avatar
  • Badge
  • Breadcrumb
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Data Table
  • Date Picker
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Menubar
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Questionnaire
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Sonner
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toggle
  • Toggle Group
  • Tooltip

Chat

  • Attachment
  • Bubble
  • Marker
  • Message
  • Message Scroller

Components

Sidebar

A composable, themeable app sidebar that collapses to icons, slides off-canvas, or floats — with header, footer, groups, menus, and nested submenus. On mobile it renders inside a Sheet.

Playground

Platform
Dashboard
—side
—variant
—collapsible
Usage
SidebarProvider owns the open state (persisted in a cookie) and wraps the whole layout; Sidebar holds SidebarHeader, a scrollable SidebarContent, and SidebarFooter; SidebarInset wraps the page content; SidebarTrigger toggles from anywhere inside the provider, and SidebarRail makes the sidebar edge clickable. In a real app the sidebar spans the full viewport — these previews scope it to a frame.
Platform
Home
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
} from "@workspace/ui/components/sidebar"

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <SidebarProvider>
      <AppSidebar />
      <SidebarInset>
        <header className="flex h-12 items-center gap-2 border-b px-4">
          <SidebarTrigger className="-ms-1" />
        </header>
        {children}
      </SidebarInset>
    </SidebarProvider>
  )
}

function AppSidebar() {
  return (
    <Sidebar>
      <SidebarHeader>{/* brand */}</SidebarHeader>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Platform</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              {items.map((item) => (
                <SidebarMenuItem key={item.title}>
                  <SidebarMenuButton asChild isActive={item.isActive}>
                    <a href={item.url}>
                      <item.icon />
                      <span>{item.title}</span>
                    </a>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              ))}
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter>{/* user */}</SidebarFooter>
      <SidebarRail />
    </Sidebar>
  )
}
Groups
SidebarContent stacks SidebarGroups — each with an optional SidebarGroupLabel and a SidebarGroupAction pinned to the end edge (an add button here). SidebarSeparator divides groups, and SidebarInput drops a search field into the header. Labels and actions hide automatically when the sidebar collapses to icons.
Projects
Support
Projects
<Sidebar>
  <SidebarHeader>
    <SidebarInput placeholder="Search..." />
  </SidebarHeader>
  <SidebarContent>
    <SidebarGroup>
      <SidebarGroupLabel>Projects</SidebarGroupLabel>
      <SidebarGroupAction title="Add project">
        <PlusIcon />
        <span className="sr-only">Add project</span>
      </SidebarGroupAction>
      <SidebarGroupContent>
        <SidebarMenu>{/* items */}</SidebarMenu>
      </SidebarGroupContent>
    </SidebarGroup>
    <SidebarSeparator />
    <SidebarGroup>
      <SidebarGroupLabel>Support</SidebarGroupLabel>
      <SidebarGroupContent>
        <SidebarMenu>{/* items */}</SidebarMenu>
      </SidebarGroupContent>
    </SidebarGroup>
  </SidebarContent>
</Sidebar>
Submenu
Nest a SidebarMenuSub inside a Collapsible for expandable sections: the CollapsibleTrigger wraps the SidebarMenuButton, and the caret rotates via the group/collapsible data state. SidebarMenuSubButton renders an anchor by default and takes isActive and size just like its parent.
Documentation
    • Button
    • Sidebar
    • Tooltip
Components
<SidebarMenu>
  {items.map((item) => (
    <Collapsible
      key={item.title}
      asChild
      defaultOpen={item.isActive}
      className="group/collapsible"
    >
      <SidebarMenuItem>
        <CollapsibleTrigger asChild>
          <SidebarMenuButton>
            <item.icon />
            <span>{item.title}</span>
            <CaretRightIcon className="ms-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
          </SidebarMenuButton>
        </CollapsibleTrigger>
        <CollapsibleContent>
          <SidebarMenuSub>
            {item.items.map((subItem) => (
              <SidebarMenuSubItem key={subItem}>
                <SidebarMenuSubButton href="#">
                  <span>{subItem}</span>
                </SidebarMenuSubButton>
              </SidebarMenuSubItem>
            ))}
          </SidebarMenuSub>
        </CollapsibleContent>
      </SidebarMenuItem>
    </Collapsible>
  ))}
</SidebarMenu>
Badges and actions
SidebarMenuBadge pins a count to the end edge of a menu item, and SidebarMenuAction adds a secondary button — showOnHover keeps it hidden until the item is hovered or focused. Both stay clear of the menu button, which reserves end padding whenever an action is present.
Mail
  • 24
  • 3
Projects
Inbox
<SidebarMenuItem>
  <SidebarMenuButton isActive>
    <TrayIcon />
    <span>Inbox</span>
  </SidebarMenuButton>
  <SidebarMenuBadge>24</SidebarMenuBadge>
</SidebarMenuItem>

<SidebarMenuItem>
  <SidebarMenuButton>
    <FolderIcon />
    <span>Website Redesign</span>
  </SidebarMenuButton>
  <SidebarMenuAction showOnHover title="More">
    <DotsThreeIcon />
    <span className="sr-only">More</span>
  </SidebarMenuAction>
</SidebarMenuItem>
Icon collapse
collapsible="icon" shrinks the sidebar to a rail of icons instead of hiding it — pass tooltip to each SidebarMenuButton so labels surface on hover while collapsed. This preview starts collapsed (defaultOpen={false} on the provider); expand it with the trigger or the rail.
Platform
Home
<SidebarProvider defaultOpen={false}>
  <Sidebar collapsible="icon">
    <SidebarContent>
      <SidebarGroup>
        <SidebarGroupContent>
          <SidebarMenu>
            {items.map((item) => (
              <SidebarMenuItem key={item.title}>
                <SidebarMenuButton tooltip={item.title}>
                  <item.icon />
                  <span>{item.title}</span>
                </SidebarMenuButton>
              </SidebarMenuItem>
            ))}
          </SidebarMenu>
        </SidebarGroupContent>
      </SidebarGroup>
    </SidebarContent>
    <SidebarRail />
  </Sidebar>
  {/* ... */}
</SidebarProvider>
Loading skeleton
SidebarMenuSkeleton fills a menu while data loads — each row gets a random text width between 50% and 90%, and showIcon adds a leading icon placeholder. This preview also shows collapsible="none": a static column with no collapse behavior.
Projects
    <Sidebar collapsible="none">
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Projects</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              {Array.from({ length: 5 }).map((_, index) => (
                <SidebarMenuItem key={index}>
                  <SidebarMenuSkeleton showIcon />
                </SidebarMenuItem>
              ))}
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
    RTL
    Menu buttons, badges, actions, and the submenu border all use logical properties, so the sidebar mirrors under a dir="rtl" ancestor — pair it with side="right" so the sidebar sits on the reading start edge. The trigger icon flips automatically.
    القائمة
    • ٤
    لوحة التحكم
    <div dir="rtl">
      <SidebarProvider>
        <Sidebar side="right">{/* ... */}</Sidebar>
        <SidebarInset>
          <header className="flex h-12 items-center gap-2 border-b px-4">
            <SidebarTrigger className="-ms-1" />
            <span className="text-sm font-medium">لوحة التحكم</span>
          </header>
        </SidebarInset>
      </SidebarProvider>
    </div>
    Accessibility
    ⌘B / Ctrl+B toggles the sidebar (every provider on this page responds, so the shortcut flips all previews at once). SidebarTrigger carries an sr-only “Toggle Sidebar” label and SidebarRail an aria-label; the rail is removed from the tab order since the trigger covers keyboard use. Menus are semantic ul/li lists, focus rings use --sidebar-ring, and on mobile the sidebar renders in a Sheet with an sr-only title and description. Tooltips only appear while collapsed to icons.
    // Menu buttons render real <button> elements — or your own element via asChild
    <SidebarMenuButton asChild isActive={pathname === item.url}>
      <Link href={item.url}>
        <item.icon />
        <span>{item.title}</span>
      </Link>
    </SidebarMenuButton>
    API Reference
    Twenty-three parts compose the sidebar: SidebarProvider (state + context), Sidebar (the panel), layout parts (SidebarHeader, SidebarContent, SidebarFooter, SidebarInset, SidebarRail, SidebarSeparator, SidebarInput, SidebarTrigger), group parts (SidebarGroup, SidebarGroupLabel, SidebarGroupAction, SidebarGroupContent), and menu parts (SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarMenuAction, SidebarMenuBadge, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton). The useSidebar hook exposes the context for custom controls.
    ComponentPropDefault
    SidebarProviderdefaultOpen?: boolean, open?: boolean, onOpenChange?: (open) => voidtrue
    Sidebarside?: "left" | "right", variant?: "sidebar" | "floating" | "inset", collapsible?: "offcanvas" | "icon" | "none"left / sidebar / offcanvas
    SidebarMenuButtonasChild?, isActive?, variant?: "default" | "outline", size?: "default" | "sm" | "lg", tooltip?: string | TooltipContent propsdefault / default
    SidebarMenuActionasChild?, showOnHover?: booleanfalse
    SidebarMenuSubButtonasChild?, size?: "sm" | "md", isActive?md
    SidebarMenuSkeletonshowIcon?: booleanfalse
    SidebarGroupLabel / SidebarGroupActionasChild?: booleanfalse
    // Read or drive the sidebar from any descendant
    const {
      state,          // "expanded" | "collapsed"
      open,           // desktop open state
      setOpen,
      openMobile,     // mobile sheet state
      setOpenMobile,
      isMobile,
      toggleSidebar,  // toggles desktop or mobile, whichever is active
    } = useSidebar()
    
    // Widths are CSS variables — override them on the provider
    <SidebarProvider
      style={{
        "--sidebar-width": "20rem",
        "--sidebar-width-icon": "3.5rem",
      } as React.CSSProperties}
    >

    Tokens used

    14 design tokens consumed by the Sidebar component.

    • --sidebar

      Sidebar

      bg-sidebar
    • --sidebar-foreground

      Sidebar Foreground

      text-sidebar-foreground
    • --sidebar-primary

      Sidebar Primary

      bg-sidebar-primary
    • --sidebar-primary-foreground

      Sidebar Primary Foreground

      text-sidebar-primary-foreground
    • --sidebar-accent

      Sidebar Accent

      hover:bg-sidebar-accentdata-active:bg-sidebar-accent
    • --sidebar-accent-foreground

      Sidebar Accent Foreground

      hover:text-sidebar-accent-foregrounddata-active:text-sidebar-accent-foreground
    • --sidebar-border

      Sidebar Border

      border-sidebar-borderbg-sidebar-borderring-sidebar-border
    • --sidebar-ring

      Sidebar Ring

      ring-sidebar-ring
    • --background

      Background

      bg-background
    • --text-sm

      Text Small (14px)

      text-sm
    • --text-xs

      Text Extra Small (12px)

      text-xs
    • --radius-md

      Radius 0.8× (8px)

      rounded-md
    • --radius-lg

      Radius 1× (10px)

      rounded-lg
    • --spacing

      Spacing unit (4px)

      p-2px-2gap-1gap-2h-8h-7size-4w-4