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

Menubar

A visually persistent menu common in desktop applications that provides quick access to a consistent set of commands.

Playground

—children
—MenubarShortcut
—variant
—dir
Usage
Each MenubarMenu pairs a MenubarTrigger in the persistent bar with its dropdown MenubarContent. Only one menu is open at a time — arrow keys move focus across triggers and between items, and related items can be wrapped in a MenubarGroup with a MenubarSeparator between groups. A MenubarShortcut renders its hint at the end of the row; wiring the actual key handler is up to you.
import {
  Menubar,
  MenubarContent,
  MenubarGroup,
  MenubarItem,
  MenubarMenu,
  MenubarSeparator,
  MenubarShortcut,
  MenubarTrigger,
} from "@workspace/ui/components/menubar"

<Menubar>
  <MenubarMenu>
    <MenubarTrigger>File</MenubarTrigger>
    <MenubarContent>
      <MenubarGroup>
        <MenubarItem>
          New Tab <MenubarShortcut>⌘T</MenubarShortcut>
        </MenubarItem>
        <MenubarItem>New Window</MenubarItem>
      </MenubarGroup>
      <MenubarSeparator />
      <MenubarGroup>
        <MenubarItem>Share</MenubarItem>
        <MenubarItem>Print</MenubarItem>
      </MenubarGroup>
    </MenubarContent>
  </MenubarMenu>
</Menubar>
Checkbox
Use MenubarCheckboxItem for toggleable options. The item is controlled — pass checked and update it in onCheckedChange. Plain items sharing a menu with checkable ones take inset so their labels align with the indicator column.
const [fullUrls, setFullUrls] = React.useState(true)

<Menubar>
  <MenubarMenu>
    <MenubarTrigger>View</MenubarTrigger>
    <MenubarContent className="w-64">
      <MenubarCheckboxItem checked={fullUrls} onCheckedChange={setFullUrls}>
        Always Show Full URLs
      </MenubarCheckboxItem>
      <MenubarSeparator />
      <MenubarItem inset>
        Reload <MenubarShortcut>⌘R</MenubarShortcut>
      </MenubarItem>
      <MenubarItem disabled inset>
        Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
      </MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>
Radio
Use MenubarRadioGroup and MenubarRadioItem for single-select options. The group holds the selected value; the item matching it shows the indicator.
const [user, setUser] = React.useState("benoit")

<Menubar>
  <MenubarMenu>
    <MenubarTrigger>Profiles</MenubarTrigger>
    <MenubarContent>
      <MenubarRadioGroup value={user} onValueChange={setUser}>
        <MenubarRadioItem value="andy">Andy</MenubarRadioItem>
        <MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
        <MenubarRadioItem value="luis">Luis</MenubarRadioItem>
      </MenubarRadioGroup>
      <MenubarSeparator />
      <MenubarItem inset>Edit...</MenubarItem>
      <MenubarItem inset>Add Profile...</MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>
Submenu
Use MenubarSub with MenubarSubTrigger and MenubarSubContent for nested menus. The sub trigger appends a caret that flips in RTL, and the nested content opens to the side, following the reading direction.
<MenubarMenu>
  <MenubarTrigger>File</MenubarTrigger>
  <MenubarContent>
    <MenubarSub>
      <MenubarSubTrigger>Share</MenubarSubTrigger>
      <MenubarSubContent>
        <MenubarItem>Email link</MenubarItem>
        <MenubarItem>Messages</MenubarItem>
        <MenubarItem>Notes</MenubarItem>
      </MenubarSubContent>
    </MenubarSub>
    <MenubarSeparator />
    <MenubarItem>
      Print... <MenubarShortcut>⌘P</MenubarShortcut>
    </MenubarItem>
  </MenubarContent>
</MenubarMenu>
With icons
Items accept an icon before the label — svgs are sized to 16px and inherit the item color automatically. Pass variant="destructive" for irreversible actions: label and icon turn destructive, and the focus highlight follows.
import { FileIcon, FloppyDiskIcon, FolderIcon } from "@phosphor-icons/react"

<MenubarMenu>
  <MenubarTrigger>File</MenubarTrigger>
  <MenubarContent>
    <MenubarItem>
      <FileIcon />
      New File <MenubarShortcut>⌘N</MenubarShortcut>
    </MenubarItem>
    <MenubarItem>
      <FolderIcon />
      Open Folder
    </MenubarItem>
    <MenubarSeparator />
    <MenubarItem>
      <FloppyDiskIcon />
      Save <MenubarShortcut>⌘S</MenubarShortcut>
    </MenubarItem>
  </MenubarContent>
</MenubarMenu>
Label and inset
A MenubarLabel names a section without being interactive. Both the label and plain items accept inset to align with the indicator column of checkbox and radio items in the same menu.
import { MenubarLabel } from "@workspace/ui/components/menubar"

<MenubarContent>
  <MenubarLabel inset>Panels</MenubarLabel>
  <MenubarItem inset>Show Sidebar</MenubarItem>
  <MenubarItem inset>Show Terminal</MenubarItem>
</MenubarContent>
RTL
Radix reads direction from the dir prop (or a surrounding Direction provider) — a plain dir="rtl" ancestor is not enough for the keyboard navigation to flip. Styling follows via logical properties: shortcuts sit at the far end with ms-auto, indicators at the start, and the submenu caret rotates via rtl:rotate-180.
<Menubar dir="rtl">
  <MenubarMenu>
    <MenubarTrigger>ملف</MenubarTrigger>
    <MenubarContent>
      <MenubarItem>
        علامة تبويب جديدة <MenubarShortcut>⌘T</MenubarShortcut>
      </MenubarItem>
      <MenubarItem>نافذة جديدة</MenubarItem>
      <MenubarSeparator />
      <MenubarSub>
        <MenubarSubTrigger>مشاركة</MenubarSubTrigger>
        <MenubarSubContent>
          <MenubarItem>رابط البريد الإلكتروني</MenubarItem>
          <MenubarItem>الرسائل</MenubarItem>
        </MenubarSubContent>
      </MenubarSub>
    </MenubarContent>
  </MenubarMenu>
</Menubar>
Accessibility
Implements the WAI-ARIA menubar pattern. The bar is a role="menubar" with roving focus: Arrow keys move across triggers and between items, Enter, Space or Arrow Down open a menu, typing jumps to a matching item, and Escape closes and returns focus to the trigger. Contents are portaled with role="menu", and checkable items expose their state via aria-checked on menuitemcheckbox / menuitemradio roles.
<div role="menubar">
  <button role="menuitem" aria-haspopup="menu" aria-expanded="true">File</button>
  <button role="menuitem" aria-haspopup="menu">Edit</button>
</div>

<div role="menu" aria-orientation="vertical">   <!-- portaled -->
  <div role="menuitem">New Tab</div>
  <div role="menuitemcheckbox" aria-checked="true">Full URLs</div>
  <div role="menuitemradio" aria-checked="false">Andy</div>
  <div role="separator" aria-orientation="horizontal"></div>
  <div role="menuitem" aria-haspopup="menu">Share</div>
</div>
API Reference
Every part wraps the matching Radix Menubar primitive and accepts all of its props. The notable ones and the local additions:
ComponentPropDefault
Menubardir?: "ltr" | "rtl"inherited
Menubarloop?: booleanfalse
MenubarContentalign?: "start" | "center" | "end""start"
MenubarContentsideOffset?: number8
MenubarContentalignOffset?: number-4
MenubarItemvariant?: "default" | "destructive""default"
MenubarIteminset?: booleanfalse
MenubarItemdisabled?: booleanfalse
MenubarItemonSelect?: (event: Event) => void—
MenubarCheckboxItemchecked?: boolean | "indeterminate"—
MenubarCheckboxItemonCheckedChange?: (checked: boolean) => void—
MenubarRadioGroupvalue?: string, onValueChange?: (value: string) => void—
MenubarRadioItemvalue: stringrequired
MenubarLabelinset?: booleanfalse
MenubarSubTriggerinset?: booleanfalse
<Menubar data-slot="menubar" />                         // role="menubar"
<MenubarMenu data-slot="menubar-menu" />                // no DOM node
<MenubarTrigger data-slot="menubar-trigger" />          // <button role="menuitem">
<MenubarContent data-slot="menubar-content" />          // role="menu", portaled
<MenubarGroup data-slot="menubar-group" />              // role="group"
<MenubarItem data-slot="menubar-item" />                // role="menuitem"
<MenubarCheckboxItem data-slot="menubar-checkbox-item" />  // role="menuitemcheckbox"
<MenubarRadioGroup data-slot="menubar-radio-group" />   // role="group"
<MenubarRadioItem data-slot="menubar-radio-item" />     // role="menuitemradio"
<MenubarLabel data-slot="menubar-label" />
<MenubarSeparator data-slot="menubar-separator" />      // role="separator"
<MenubarShortcut data-slot="menubar-shortcut" />        // <span>
<MenubarSub data-slot="menubar-sub" />                  // no DOM node
<MenubarSubTrigger data-slot="menubar-sub-trigger" />   // role="menuitem" + caret
<MenubarSubContent data-slot="menubar-sub-content" />   // role="menu"

Tokens used

14 design tokens consumed by the Menubar component.

  • --popover

    Popover

    bg-popover
  • --popover-foreground

    Popover Foreground

    text-popover-foreground
  • --muted

    Muted

    hover:bg-mutedaria-expanded:bg-muted
  • --accent

    Accent

    focus:bg-accentdata-open:bg-accent
  • --accent-foreground

    Accent Foreground

    focus:text-accent-foregrounddata-open:text-accent-foreground
  • --destructive

    Destructive

    data-[variant=destructive]:text-destructivedata-[variant=destructive]:focus:bg-destructive/10dark:data-[variant=destructive]:focus:bg-destructive/20
  • --muted-foreground

    Muted Foreground

    text-muted-foreground
  • --foreground

    Foreground

    ring-foreground/10
  • --border

    Border

    borderbg-border
  • --radius-md

    Radius 0.8× (8px)

    rounded-md
  • --radius-sm

    Radius 0.6× (6px)

    rounded-sm
  • --text-sm

    Text Small (14px)

    text-sm
  • --text-xs

    Text Extra Small (12px)

    text-xs
  • --spacing

    Spacing unit (4px)

    h-9p-1gap-1px-2py-1.5gap-2ps-8size-4