# Menubar

> A desktop-style bar of menus with one highlight that slides between open menus and instant switching as you sweep across.

Docs: https://hextaui.com/docs/menubar
Markdown: https://hextaui.com/docs/menubar.md

```tsx title="components/examples/menubar/demo.tsx"
import { KbdGroup } from "@/components/ui/kbd"
import {
  Menubar,
  MenubarCheckboxItem,
  MenubarContent,
  MenubarGroup,
  MenubarItem,
  MenubarLabel,
  MenubarMenu,
  MenubarRadioGroup,
  MenubarRadioItem,
  MenubarSeparator,
  MenubarShortcut,
  MenubarSub,
  MenubarSubContent,
  MenubarSubTrigger,
  MenubarTrigger,
} from "@/components/ui/menubar"

export function MenubarDemo() {
  return (
    <Menubar aria-label="Editor">
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>
            New tab
            <MenubarShortcut>
              <KbdGroup keys="mod+t" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            New window
            <MenubarShortcut>
              <KbdGroup keys="mod+n" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarItem disabled>New incognito window</MenubarItem>
          <MenubarSeparator />
          <MenubarSub>
            <MenubarSubTrigger>Share</MenubarSubTrigger>
            <MenubarSubContent>
              <MenubarItem>Email link</MenubarItem>
              <MenubarItem>Messages</MenubarItem>
              <MenubarItem>Notes</MenubarItem>
            </MenubarSubContent>
          </MenubarSub>
          <MenubarSeparator />
          <MenubarItem>
            Print…
            <MenubarShortcut>
              <KbdGroup keys="mod+p" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>Edit</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>
            Undo
            <MenubarShortcut>
              <KbdGroup keys="mod+z" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            Redo
            <MenubarShortcut>
              <KbdGroup keys="mod+shift+z" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarSeparator />
          <MenubarSub>
            <MenubarSubTrigger>Find</MenubarSubTrigger>
            <MenubarSubContent>
              <MenubarItem>Search the web</MenubarItem>
              <MenubarSeparator />
              <MenubarItem>Find…</MenubarItem>
              <MenubarItem>Find next</MenubarItem>
              <MenubarItem>Find previous</MenubarItem>
            </MenubarSubContent>
          </MenubarSub>
          <MenubarSeparator />
          <MenubarItem>Cut</MenubarItem>
          <MenubarItem>Copy</MenubarItem>
          <MenubarItem>Paste</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>View</MenubarTrigger>
        <MenubarContent>
          <MenubarCheckboxItem>Always show bookmarks bar</MenubarCheckboxItem>
          <MenubarCheckboxItem defaultChecked>
            Always show full URLs
          </MenubarCheckboxItem>
          <MenubarSeparator />
          <MenubarItem inset>
            Reload
            <MenubarShortcut>
              <KbdGroup keys="mod+r" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarItem inset disabled>
            Force reload
            <MenubarShortcut>
              <KbdGroup keys="mod+shift+r" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarSeparator />
          <MenubarItem inset>Toggle full screen</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>Profiles</MenubarTrigger>
        <MenubarContent>
          <MenubarGroup>
            <MenubarLabel>Switch profile</MenubarLabel>
            <MenubarRadioGroup defaultValue="mira">
              <MenubarRadioItem value="jun">Jun</MenubarRadioItem>
              <MenubarRadioItem value="mira">Mira</MenubarRadioItem>
              <MenubarRadioItem value="sol">Sol</MenubarRadioItem>
            </MenubarRadioGroup>
          </MenubarGroup>
          <MenubarSeparator />
          <MenubarItem inset>Edit…</MenubarItem>
          <MenubarItem inset>Add profile…</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}
```

## Installation

### CLI

```bash
npx shadcn@latest add https://hextaui.com/r/menubar.json
```

This adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

### Manual

Add the HextaUI theme tokens (https://hextaui.com/docs/installation#theme) to your global CSS if you haven't yet, then install the dependencies.

```bash
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cn
```

Copy and paste the following code into your project.

```tsx title="components/ui/menubar.tsx"
"use client"

import * as React from "react"
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar"
import { cn } from "cn"

import { useSlidingHighlight } from "@/lib/motion"

import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuPortal,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  type DropdownMenuCheckboxItemProps,
  type DropdownMenuContentProps,
  type DropdownMenuItemProps,
  type DropdownMenuLabelProps,
  type DropdownMenuRadioItemProps,
  type DropdownMenuSubTriggerProps,
} from "@/components/ui/dropdown-menu"

type ClassName<State> =
  string | ((state: State) => string | undefined) | undefined

function mergeClassName<State>(base: string, className: ClassName<State>) {
  return typeof className === "function"
    ? (state: State) => cn(base, className(state))
    : cn(base, className)
}

type MenubarProps = MenubarPrimitive.Props

function Menubar({ className, children, ref, ...props }: MenubarProps) {
  const barRef = React.useRef<HTMLDivElement | null>(null)
  const highlightRef = React.useRef<HTMLSpanElement | null>(null)
  useSlidingHighlight(
    barRef,
    highlightRef,
    "[data-slot=menubar-trigger][data-popup-open]"
  )

  const setRef = React.useCallback(
    (node: HTMLDivElement | null) => {
      barRef.current = node
      if (typeof ref === "function") {
        return ref(node)
      }
      if (ref) {
        ref.current = node
      }
      return undefined
    },
    [ref]
  )

  return (
    <MenubarPrimitive
      ref={setRef}
      data-slot="menubar"
      className={mergeClassName(
        "group/menubar relative isolate flex h-9 w-fit max-w-full [scrollbar-width:none] items-center gap-0.5 overflow-x-auto overscroll-none rounded-lg bg-background p-1 ring-(length:--hairline) ring-border [--menubar-radius:var(--radius-lg)] [--menubar-trigger-radius:max(calc(var(--radius-sm)*0.5),calc(var(--menubar-radius)-0.25rem))] data-[orientation=vertical]:h-auto data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch forced-colors:border pointer-coarse:h-11",
        className
      )}
      {...props}
    >
      <span
        ref={highlightRef}
        aria-hidden="true"
        data-slot="menubar-highlight"
        className="pointer-events-none absolute top-0 -z-1 rounded-(--menubar-trigger-radius) bg-accent opacity-0 transition-[transform,width,height,opacity] duration-200 ease-out-quint data-instant:transition-opacity data-visible:opacity-100 motion-reduce:transition-opacity"
      />
      {children}
    </MenubarPrimitive>
  )
}

function MenubarMenu<Payload>(props: MenuPrimitive.Root.Props<Payload>) {
  return <DropdownMenu {...props} />
}

function MenubarTrigger<Payload>({
  className,
  children,
  ...props
}: MenuPrimitive.Trigger.Props<Payload>) {
  const iconOnly =
    React.Children.count(children) === 1 &&
    React.isValidElement(children) &&
    (typeof children.type !== "string" || children.type === "svg")

  return (
    <MenuPrimitive.Trigger
      data-slot="menubar-trigger"
      data-icon-only={iconOnly ? "" : undefined}
      className={mergeClassName(
        "flex h-7 shrink-0 items-center gap-1.5 rounded-(--menubar-trigger-radius) px-2.5 text-sm font-medium whitespace-nowrap outline-none select-none focus-visible:ring-3 focus-visible:inset-ring-(length:--hairline) focus-visible:ring-focus-ring focus-visible:inset-ring-ring focus-visible:outline-hidden not-data-icon-only:has-[>svg:first-child]:ps-2 data-icon-only:aspect-square data-icon-only:justify-center data-icon-only:px-0 data-popup-open:text-accent-foreground pointer-coarse:h-9 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&>svg:not([class*='text-'])]:text-muted-foreground data-popup-open:[&>svg:not([class*='text-'])]:text-accent-foreground [@media(hover:hover)]:group-not-data-has-submenu-open/menubar:hover:bg-muted/70",
        className
      )}
      {...props}
    >
      {children}
    </MenuPrimitive.Trigger>
  )
}

function MenubarContent({
  className,
  align = "start",
  sideOffset = 8,
  alignOffset = -4,
  ...props
}: DropdownMenuContentProps) {
  return (
    <DropdownMenuContent
      data-slot="menubar-content"
      align={align}
      sideOffset={sideOffset}
      alignOffset={alignOffset}
      className={mergeClassName(
        "min-w-48 data-[instant=group]:transition-none",
        className
      )}
      {...props}
    />
  )
}

function MenubarPortal(props: MenuPrimitive.Portal.Props) {
  return <DropdownMenuPortal data-slot="menubar-portal" {...props} />
}

function MenubarGroup(props: MenuPrimitive.Group.Props) {
  return <DropdownMenuGroup data-slot="menubar-group" {...props} />
}

function MenubarLabel(props: DropdownMenuLabelProps) {
  return <DropdownMenuLabel data-slot="menubar-label" {...props} />
}

function MenubarItem(props: DropdownMenuItemProps) {
  return <DropdownMenuItem data-slot="menubar-item" {...props} />
}

function MenubarCheckboxItem(props: DropdownMenuCheckboxItemProps) {
  return (
    <DropdownMenuCheckboxItem data-slot="menubar-checkbox-item" {...props} />
  )
}

function MenubarRadioGroup(props: MenuPrimitive.RadioGroup.Props) {
  return <DropdownMenuRadioGroup data-slot="menubar-radio-group" {...props} />
}

function MenubarRadioItem(props: DropdownMenuRadioItemProps) {
  return <DropdownMenuRadioItem data-slot="menubar-radio-item" {...props} />
}

function MenubarSeparator(props: MenuPrimitive.Separator.Props) {
  return <DropdownMenuSeparator data-slot="menubar-separator" {...props} />
}

function MenubarShortcut(props: React.ComponentProps<"kbd">) {
  return <DropdownMenuShortcut data-slot="menubar-shortcut" {...props} />
}

function MenubarSub(props: MenuPrimitive.SubmenuRoot.Props) {
  return <DropdownMenuSub {...props} />
}

function MenubarSubTrigger(props: DropdownMenuSubTriggerProps) {
  return <DropdownMenuSubTrigger data-slot="menubar-sub-trigger" {...props} />
}

function MenubarSubContent(props: DropdownMenuContentProps) {
  return <DropdownMenuSubContent data-slot="menubar-sub-content" {...props} />
}

export {
  Menubar,
  MenubarCheckboxItem,
  MenubarContent,
  MenubarGroup,
  MenubarItem,
  MenubarLabel,
  MenubarMenu,
  MenubarPortal,
  MenubarRadioGroup,
  MenubarRadioItem,
  MenubarSeparator,
  MenubarShortcut,
  MenubarSub,
  MenubarSubContent,
  MenubarSubTrigger,
  MenubarTrigger,
}
export type { MenubarProps }
```

```tsx title="components/ui/dropdown-menu.tsx"
"use client"

import * as React from "react"
import {
  DirectionProvider,
  useDirection,
  type TextDirection,
} from "@base-ui/react/direction-provider"
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import {
  IconCheck,
  IconChevronRight,
  IconCircleFilled,
} from "@tabler/icons-react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"

import { SafeAreaOverlay } from "@/components/ui/safe-area"

type ClassName<State> =
  string | ((state: State) => string | undefined) | undefined

function mergeClassName<State>(base: string, className: ClassName<State>) {
  return typeof className === "function"
    ? (state: State) => cn(base, className(state))
    : cn(base, className)
}

type DropdownMenuContextValue = {
  direction: TextDirection
  chosen: string | null
  choose: (id: string) => void
}

const DropdownMenuContext =
  React.createContext<DropdownMenuContextValue | null>(null)

const DropdownMenuGroupContext = React.createContext(false)

function useDropdownMenuContext(part: string) {
  const context = React.useContext(DropdownMenuContext)
  if (!context) {
    throw new Error(`${part} must be used within <DropdownMenu>.`)
  }
  return context
}

function readDirection(element: Element | null | undefined) {
  if (!element || !element.isConnected) {
    return undefined
  }
  return getComputedStyle(element).direction === "rtl" ? "rtl" : "ltr"
}

function getSubmenuElements() {
  const triggers = document.querySelectorAll(
    "[data-slot=dropdown-menu-sub-trigger][data-popup-open]"
  )
  const reference = triggers[triggers.length - 1]
  const id = reference?.getAttribute("aria-controls")
  const floating = id ? document.getElementById(id) : null
  return reference && floating ? { reference, floating } : null
}

type DropdownMenuProps<Payload> = MenuPrimitive.Root.Props<Payload> & {
  showSafeArea?: boolean
}

function DropdownMenu<Payload>({
  onOpenChange,
  onOpenChangeComplete,
  showSafeArea = false,
  children,
  ...props
}: DropdownMenuProps<Payload>) {
  const [open, setOpen] = React.useState(false)
  const inherited = useDirection()
  const [triggerDirection, setTriggerDirection] =
    React.useState<TextDirection>()
  const [chosen, setChosen] = React.useState<string | null>(null)
  const direction = triggerDirection === "rtl" ? "rtl" : inherited

  const context = React.useMemo(
    () => ({ direction, chosen, choose: setChosen }),
    [direction, chosen]
  )

  return (
    <DropdownMenuContext.Provider value={context}>
      <DirectionProvider direction={direction}>
        <MenuPrimitive.Root
          onOpenChange={(open, eventDetails) => {
            onOpenChange?.(open, eventDetails)
            if (!eventDetails.isCanceled) {
              setOpen(open)
            }
            if (open && !eventDetails.isCanceled) {
              setChosen(null)
              if (eventDetails.trigger) {
                setTriggerDirection(readDirection(eventDetails.trigger))
              }
            }
          }}
          onOpenChangeComplete={(open) => {
            onOpenChangeComplete?.(open)
            if (!open) {
              setChosen(null)
            }
          }}
          {...props}
        >
          {children}
        </MenuPrimitive.Root>
        {showSafeArea && (props.open ?? open) ? (
          <SafeAreaOverlay getElements={getSubmenuElements} />
        ) : null}
      </DirectionProvider>
    </DropdownMenuContext.Provider>
  )
}

function DropdownMenuTrigger<Payload>(
  props: MenuPrimitive.Trigger.Props<Payload>
) {
  return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
}

function DropdownMenuPortal(props: MenuPrimitive.Portal.Props) {
  return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
}

type DropdownMenuContentProps = MenuPrimitive.Popup.Props &
  Pick<
    MenuPrimitive.Positioner.Props,
    | "side"
    | "align"
    | "sideOffset"
    | "alignOffset"
    | "anchor"
    | "collisionPadding"
    | "collisionAvoidance"
    | "sticky"
  > & {
    portalProps?: Omit<MenuPrimitive.Portal.Props, "children">
  }

const dropdownMenuContentClassName =
  "relative max-h-(--available-height) max-w-[min(var(--available-width),20rem)] min-w-40 origin-(--transform-origin) overflow-x-hidden overflow-y-auto overscroll-none rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-(length:--hairline) forced-colors:border ring-foreground/10 transition-[opacity,scale] duration-150 ease-out-quint outline-none focus-visible:outline-hidden [--dropdown-menu-item-radius:max(calc(var(--radius-sm)*0.5),calc(var(--radius-lg)-0.25rem))] data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:data-instant:transition-none motion-safe:data-starting-style:scale-96 motion-safe:data-[chosen]:data-ending-style:delay-120 motion-reduce:transition-opacity"

const SIDE_COLLISION_AVOIDANCE = { fallbackAxisSide: "end" } as const

function DropdownMenuPopup({
  slot,
  className,
  side,
  align,
  sideOffset,
  alignOffset,
  anchor,
  collisionPadding = 8,
  collisionAvoidance,
  sticky,
  portalProps,
  ...props
}: DropdownMenuContentProps & { slot: string }) {
  const { direction, chosen } = useDropdownMenuContext(
    slot === "dropdown-menu-content"
      ? "DropdownMenuContent"
      : "DropdownMenuSubContent"
  )

  return (
    <MenuPrimitive.Portal {...portalProps}>
      <MenuPrimitive.Positioner
        data-slot={slot.replace("content", "positioner")}
        dir={direction === "rtl" ? "rtl" : undefined}
        side={side}
        align={align}
        sideOffset={sideOffset}
        alignOffset={alignOffset}
        anchor={anchor}
        collisionPadding={collisionPadding}
        collisionAvoidance={
          collisionAvoidance ??
          (side === "bottom" || side === "top"
            ? undefined
            : SIDE_COLLISION_AVOIDANCE)
        }
        sticky={sticky}
        className="isolate z-50 outline-none focus-visible:outline-hidden"
      >
        <MenuPrimitive.Popup
          data-slot={slot}
          data-chosen={chosen === null ? undefined : ""}
          className={mergeClassName(
            cn(
              dropdownMenuContentClassName,
              slot === "dropdown-menu-content" &&
                "min-w-[max(var(--anchor-width),10rem)]"
            ),
            className
          )}
          {...props}
        />
      </MenuPrimitive.Positioner>
    </MenuPrimitive.Portal>
  )
}

function DropdownMenuContent({
  side = "bottom",
  align = "start",
  sideOffset = 4,
  alignOffset = 0,
  ...props
}: DropdownMenuContentProps) {
  return (
    <DropdownMenuPopup
      slot="dropdown-menu-content"
      side={side}
      align={align}
      sideOffset={sideOffset}
      alignOffset={alignOffset}
      {...props}
    />
  )
}

function DropdownMenuGroup(props: MenuPrimitive.Group.Props) {
  return (
    <DropdownMenuGroupContext.Provider value>
      <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
    </DropdownMenuGroupContext.Provider>
  )
}

type DropdownMenuLabelProps = MenuPrimitive.GroupLabel.Props & {
  inset?: boolean
}

const dropdownMenuLabelClassName =
  "px-2 pt-2 pb-1 text-xs font-medium text-muted-foreground wrap-anywhere data-inset:ps-8"

function DropdownMenuStandaloneLabel({
  className,
  render,
  ...props
}: MenuPrimitive.GroupLabel.Props) {
  const resolved = mergeClassName(dropdownMenuLabelClassName, className)

  return useRender({
    defaultTagName: "div",
    render,
    props: mergeProps<"div">(
      {
        className: typeof resolved === "function" ? resolved({}) : resolved,
      },
      props as React.ComponentProps<"div">
    ),
  })
}

function DropdownMenuLabel({ inset, ...props }: DropdownMenuLabelProps) {
  const inGroup = React.useContext(DropdownMenuGroupContext)
  const Label = inGroup ? MenuPrimitive.GroupLabel : DropdownMenuStandaloneLabel

  return (
    <Label
      data-slot="dropdown-menu-label"
      data-inset={inset ? "" : undefined}
      {...props}
      className={
        inGroup
          ? mergeClassName(dropdownMenuLabelClassName, props.className)
          : props.className
      }
    />
  )
}

const dropdownMenuItemVariants = cva(
  "relative flex min-h-8 w-full min-w-0 cursor-default items-center gap-2 rounded-(--dropdown-menu-item-radius) px-2 py-1.5 text-start text-sm wrap-anywhere outline-none select-none focus-visible:outline-hidden data-highlighted:bg-accent data-highlighted:text-accent-foreground data-inset:ps-8 motion-safe:data-[chosen]:animate-menu-blink forced-colors:data-highlighted:outline-2 forced-colors:data-highlighted:-outline-offset-2 forced-colors:data-highlighted:outline-solid pointer-coarse:min-h-11 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:[&_svg:not([class*='text-'])]:text-accent-foreground",
  {
    variants: {
      variant: {
        default: "",
        destructive:
          "text-destructive [--menu-blink:color-mix(in_oklab,var(--color-destructive)_10%,transparent)] data-highlighted:bg-destructive/10 data-highlighted:text-destructive dark:[--menu-blink:color-mix(in_oklab,var(--color-destructive)_20%,transparent)] dark:data-highlighted:bg-destructive/20 forced-colors:data-highlighted:outline-2 forced-colors:data-highlighted:-outline-offset-2 forced-colors:data-highlighted:outline-solid [&_svg:not([class*='text-'])]:text-destructive data-highlighted:[&_svg:not([class*='text-'])]:text-destructive",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

type ChoosableEvent = React.MouseEvent<HTMLElement> & {
  baseUIHandlerPrevented?: boolean
}

function useChooseOnClick<E extends ChoosableEvent>(
  closeOnClick: boolean,
  onClick: ((event: E) => void) | undefined
) {
  const context = React.useContext(DropdownMenuContext)
  const id = React.useId()

  return {
    chosen: context?.chosen === id ? "" : undefined,
    onClick: (event: E) => {
      onClick?.(event)
      if (
        context &&
        closeOnClick &&
        event.detail !== 0 &&
        !event.defaultPrevented &&
        !event.baseUIHandlerPrevented
      ) {
        context.choose(id)
      }
    },
  }
}

type DropdownMenuItemProps = MenuPrimitive.Item.Props &
  VariantProps<typeof dropdownMenuItemVariants> & {
    inset?: boolean
  }

function DropdownMenuItem({
  className,
  variant = "default",
  inset,
  closeOnClick = true,
  onClick,
  ...props
}: DropdownMenuItemProps) {
  const choice = useChooseOnClick(closeOnClick, onClick)

  return (
    <MenuPrimitive.Item
      data-slot="dropdown-menu-item"
      data-variant={variant}
      data-inset={inset ? "" : undefined}
      data-chosen={choice.chosen}
      closeOnClick={closeOnClick}
      onClick={choice.onClick}
      className={mergeClassName(
        dropdownMenuItemVariants({ variant }),
        className
      )}
      {...props}
    />
  )
}

const dropdownMenuIndicatorClassName =
  "flex items-center justify-center opacity-0 transition-[opacity,scale] duration-150 ease-out-quint data-checked:opacity-100 motion-safe:scale-50 motion-safe:data-checked:scale-100 motion-reduce:transition-none"

type DropdownMenuCheckboxItemProps = MenuPrimitive.CheckboxItem.Props & {
  inset?: boolean
}

function DropdownMenuCheckboxItem({
  className,
  children,
  inset,
  closeOnClick = false,
  onClick,
  ...props
}: DropdownMenuCheckboxItemProps) {
  const choice = useChooseOnClick(closeOnClick, onClick)

  return (
    <MenuPrimitive.CheckboxItem
      data-slot="dropdown-menu-checkbox-item"
      data-inset={inset ? "" : undefined}
      data-chosen={choice.chosen}
      closeOnClick={closeOnClick}
      onClick={choice.onClick}
      className={mergeClassName(
        cn(dropdownMenuItemVariants(), "ps-8"),
        className
      )}
      {...props}
    >
      <span
        aria-hidden
        className="pointer-events-none absolute start-2 flex size-4 items-center justify-center"
      >
        <MenuPrimitive.CheckboxItemIndicator
          keepMounted
          data-slot="dropdown-menu-checkbox-item-indicator"
          className={dropdownMenuIndicatorClassName}
        >
          <IconCheck className="text-current" />
        </MenuPrimitive.CheckboxItemIndicator>
      </span>
      {children}
    </MenuPrimitive.CheckboxItem>
  )
}

function DropdownMenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) {
  return (
    <DropdownMenuGroupContext.Provider value>
      <MenuPrimitive.RadioGroup
        data-slot="dropdown-menu-radio-group"
        {...props}
      />
    </DropdownMenuGroupContext.Provider>
  )
}

type DropdownMenuRadioItemProps = MenuPrimitive.RadioItem.Props & {
  inset?: boolean
}

function DropdownMenuRadioItem({
  className,
  children,
  inset,
  closeOnClick = false,
  onClick,
  ...props
}: DropdownMenuRadioItemProps) {
  const choice = useChooseOnClick(closeOnClick, onClick)

  return (
    <MenuPrimitive.RadioItem
      data-slot="dropdown-menu-radio-item"
      data-inset={inset ? "" : undefined}
      data-chosen={choice.chosen}
      closeOnClick={closeOnClick}
      onClick={choice.onClick}
      className={mergeClassName(
        cn(dropdownMenuItemVariants(), "ps-8"),
        className
      )}
      {...props}
    >
      <span
        aria-hidden
        className="pointer-events-none absolute start-2 flex size-4 items-center justify-center"
      >
        <MenuPrimitive.RadioItemIndicator
          keepMounted
          data-slot="dropdown-menu-radio-item-indicator"
          className={dropdownMenuIndicatorClassName}
        >
          <IconCircleFilled className="size-2 text-current" />
        </MenuPrimitive.RadioItemIndicator>
      </span>
      {children}
    </MenuPrimitive.RadioItem>
  )
}

function DropdownMenuSeparator({
  className,
  ...props
}: MenuPrimitive.Separator.Props) {
  return (
    <MenuPrimitive.Separator
      data-slot="dropdown-menu-separator"
      className={mergeClassName("-mx-1 my-1 h-px bg-border", className)}
      {...props}
    />
  )
}

function DropdownMenuShortcut({
  className,
  dir = "ltr",
  children,
  ...props
}: React.ComponentProps<"kbd">) {
  return (
    <kbd
      data-slot="dropdown-menu-shortcut"
      className={cn(
        "ms-auto shrink-0 ps-3 font-sans text-xs tracking-widest text-muted-foreground in-data-highlighted:text-accent-foreground/70 in-data-[variant=destructive]:text-destructive/70",
        className
      )}
      {...props}
    >
      <span dir={dir} className="[unicode-bidi:isolate]">
        {children}
      </span>
    </kbd>
  )
}

function DropdownMenuSub(props: MenuPrimitive.SubmenuRoot.Props) {
  return <MenuPrimitive.SubmenuRoot {...props} />
}

type DropdownMenuSubTriggerProps = MenuPrimitive.SubmenuTrigger.Props & {
  inset?: boolean
}

function DropdownMenuSubTrigger({
  className,
  children,
  inset,
  ...props
}: DropdownMenuSubTriggerProps) {
  return (
    <MenuPrimitive.SubmenuTrigger
      data-slot="dropdown-menu-sub-trigger"
      data-inset={inset ? "" : undefined}
      className={mergeClassName(
        cn(
          dropdownMenuItemVariants(),
          "data-popup-open:bg-accent data-popup-open:text-accent-foreground forced-colors:data-popup-open:outline-2 forced-colors:data-popup-open:-outline-offset-2 forced-colors:data-popup-open:outline-solid"
        ),
        className
      )}
      {...props}
    >
      {children}
      <IconChevronRight
        aria-hidden
        data-slot="dropdown-menu-sub-trigger-icon"
        className="ms-auto rtl:-scale-x-100"
      />
    </MenuPrimitive.SubmenuTrigger>
  )
}

function DropdownMenuSubContent({
  sideOffset = 0,
  alignOffset = -4,
  ...props
}: DropdownMenuContentProps) {
  return (
    <DropdownMenuPopup
      slot="dropdown-menu-sub-content"
      sideOffset={sideOffset}
      alignOffset={alignOffset}
      {...props}
    />
  )
}

const createDropdownMenuHandle = MenuPrimitive.createHandle

export {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuPortal,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
  createDropdownMenuHandle,
  dropdownMenuItemVariants,
}
export type {
  DropdownMenuProps,
  DropdownMenuContentProps,
  DropdownMenuLabelProps,
  DropdownMenuItemProps,
  DropdownMenuCheckboxItemProps,
  DropdownMenuRadioItemProps,
  DropdownMenuSubTriggerProps,
}
```

```ts title="lib/motion.ts"
import * as React from "react"

const easeOut = "cubic-bezier(0.23, 1, 0.32, 1)"
const easeInOut = "cubic-bezier(0.77, 0, 0.175, 1)"
const easeSpring =
  "linear(0, 0.015 2%, 0.0532 4%, 0.1065 6%, 0.1686 8%, 0.2351 10%, 0.3363 13%, 0.4332 16%, 0.5495 20%, 0.648 24%, 0.7287 28%, 0.807 33%, 0.8646 38%, 0.9128 44%, 0.9447 50%, 0.968 57%, 0.9834 65%, 0.9924 74%, 0.9975 85%, 1)"

const duration = {
  press: 100,
  release: 200,
  hover: 150,
  enter: 200,
  exit: 150,
  morph: 300,
} as const

function prefersReducedMotion() {
  return (
    typeof window === "undefined" ||
    typeof window.matchMedia !== "function" ||
    window.matchMedia("(prefers-reduced-motion: reduce)").matches
  )
}

type SizeAxis = "width" | "height"

type SizeMorphOptions = {
  axis: SizeAxis
  enabled?: boolean
  duration?: number
  easing?: string
}

function readSize(element: HTMLElement, axis: SizeAxis) {
  const value = parseFloat(getComputedStyle(element)[axis])
  if (Number.isFinite(value)) {
    return value
  }
  const rect = element.getBoundingClientRect()
  return axis === "width" ? rect.width : rect.height
}

function attachSizeMorph(
  element: HTMLElement,
  axis: SizeAxis,
  time: number,
  easing: string
) {
  if (
    typeof MutationObserver === "undefined" ||
    typeof element.animate !== "function"
  ) {
    return undefined
  }

  let settled = readSize(element, axis)
  let animation: Animation | null = null

  const morph = () => {
    const from = animation ? readSize(element, axis) : settled
    animation?.cancel()
    animation = null
    const to = readSize(element, axis)
    settled = to

    if (
      Math.abs(from - to) < 0.5 ||
      prefersReducedMotion() ||
      !element.isConnected ||
      element.getClientRects().length === 0
    ) {
      element.removeAttribute("data-morphing")
      return
    }

    element.setAttribute("data-morphing", "")
    const running = element.animate(
      [{ [axis]: `${from}px` }, { [axis]: `${to}px` }],
      { duration: time, easing }
    )
    animation = running
    running.onfinish = () => {
      if (animation === running) {
        animation = null
        element.removeAttribute("data-morphing")
        settled = readSize(element, axis)
      }
    }
  }

  const mutations = new MutationObserver(morph)
  mutations.observe(element, {
    childList: true,
    subtree: true,
    characterData: true,
  })

  const resize =
    typeof ResizeObserver === "undefined"
      ? null
      : new ResizeObserver(() => {
          if (!animation) {
            settled = readSize(element, axis)
          }
        })
  resize?.observe(element)

  return () => {
    mutations.disconnect()
    resize?.disconnect()
    animation?.cancel()
    element.removeAttribute("data-morphing")
  }
}

function useSizeMorph<T extends HTMLElement>({
  axis,
  enabled = true,
  duration: time = duration.morph,
  easing = easeOut,
}: SizeMorphOptions): React.RefCallback<T> {
  return React.useCallback(
    (element: T | null) => {
      if (!element || !enabled) {
        return undefined
      }
      return attachSizeMorph(element, axis, time, easing)
    },
    [axis, enabled, time, easing]
  )
}

function useSlidingHighlight(
  barRef: React.RefObject<HTMLElement | null>,
  highlightRef: React.RefObject<HTMLElement | null>,
  selector: string,
  attribute = "data-popup-open"
) {
  React.useLayoutEffect(() => {
    const bar = barRef.current
    const highlight = highlightRef.current
    if (!bar || !highlight) {
      return
    }

    let current: HTMLElement | null = null

    const place = (trigger: HTMLElement, instant: boolean) => {
      if (instant || prefersReducedMotion()) {
        highlight.setAttribute("data-instant", "")
      } else {
        highlight.removeAttribute("data-instant")
      }
      const frame = bar.getBoundingClientRect()
      const box = trigger.getBoundingClientRect()
      const scale = bar.offsetWidth > 0 ? frame.width / bar.offsetWidth : 1
      highlight.style.left = "0px"
      highlight.style.width = `${box.width / scale}px`
      highlight.style.height = `${box.height / scale}px`
      highlight.style.transform = `translate(${(box.left - frame.left) / scale - bar.clientLeft}px, ${(box.top - frame.top) / scale - bar.clientTop}px)`
    }

    const sync = () => {
      const trigger = bar.querySelector<HTMLElement>(selector)
      if (trigger === current) {
        if (trigger) {
          place(trigger, true)
        }
        return
      }
      const appearing = current === null
      current = trigger
      if (!trigger) {
        highlight.removeAttribute("data-visible")
        return
      }
      place(trigger, appearing)
      if (appearing) {
        void highlight.offsetWidth
      }
      highlight.setAttribute("data-visible", "")
    }

    sync()
    const mutations = new MutationObserver(sync)
    mutations.observe(bar, {
      subtree: true,
      childList: true,
      attributes: true,
      attributeFilter: [attribute],
    })
    const resize =
      typeof ResizeObserver === "undefined"
        ? null
        : new ResizeObserver(() => {
            if (current) {
              place(current, true)
            }
          })
    resize?.observe(bar)
    const onScroll = () => {
      if (current) {
        place(current, true)
      }
    }
    bar.addEventListener("scroll", onScroll, { capture: true, passive: true })

    return () => {
      mutations.disconnect()
      resize?.disconnect()
      bar.removeEventListener("scroll", onScroll, { capture: true })
    }
  }, [barRef, highlightRef, selector, attribute])
}

export {
  duration,
  easeInOut,
  easeOut,
  easeSpring,
  prefersReducedMotion,
  useSizeMorph,
  useSlidingHighlight,
}
export type { SizeMorphOptions }
```

Update the import paths to match your project setup.

## Usage

```tsx
import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarShortcut,
  MenubarTrigger,
} from "@/components/ui/menubar"
```

```tsx
<Menubar aria-label="Editor">
  <MenubarMenu>
    <MenubarTrigger>File</MenubarTrigger>
    <MenubarContent>
      <MenubarItem>
        New tab <MenubarShortcut>⌘T</MenubarShortcut>
      </MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>
```

Each `<MenubarMenu />` is a Dropdown menu, so items, checkbox and radio items, submenus, labels and shortcuts work and look exactly the same. Open a menu, then move the pointer or press the arrow keys to sweep across the bar.

## Composition

```text
Menubar
└── MenubarMenu
    ├── MenubarTrigger
    └── MenubarContent
        ├── MenubarGroup
        │   ├── MenubarLabel
        │   └── MenubarItem
        │       └── MenubarShortcut
        ├── MenubarCheckboxItem
        ├── MenubarRadioGroup
        │   └── MenubarRadioItem
        ├── MenubarSeparator
        └── MenubarSub
            ├── MenubarSubTrigger
            └── MenubarSubContent
```

## Examples

### Trigger icons

Put an icon before a trigger's label, or use an icon on its own with an `aria-label`. Icon-only triggers get a square shape.

```tsx title="components/examples/menubar/trigger-icons.tsx"
import {
  IconAdjustmentsHorizontal,
  IconDots,
  IconFolder,
  IconLayoutGrid,
  IconPencil,
} from "@tabler/icons-react"

import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarSeparator,
  MenubarTrigger,
} from "@/components/ui/menubar"

export function MenubarTriggerIcons() {
  return (
    <Menubar aria-label="Project">
      <MenubarMenu>
        <MenubarTrigger>
          <IconFolder />
          Project
        </MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Open…</MenubarItem>
          <MenubarItem>Duplicate</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>
          <IconPencil />
          Edit
        </MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Rename</MenubarItem>
          <MenubarItem>Move</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>
          <IconLayoutGrid />
          View
        </MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Grid</MenubarItem>
          <MenubarItem>List</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger aria-label="More">
          <IconDots />
        </MenubarTrigger>
        <MenubarContent align="end">
          <MenubarItem>
            <IconAdjustmentsHorizontal />
            Preferences
          </MenubarItem>
          <MenubarSeparator />
          <MenubarItem variant="destructive">Delete project</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}
```

### Icons

Put an icon before the label. Use `variant="destructive"` on items that delete or can't be undone.

```tsx title="components/examples/menubar/icons.tsx"
import {
  IconCopy,
  IconDownload,
  IconFilePlus,
  IconFolderOpen,
  IconScissors,
  IconTrash,
} from "@tabler/icons-react"

import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarSeparator,
  MenubarShortcut,
  MenubarTrigger,
} from "@/components/ui/menubar"

export function MenubarIcons() {
  return (
    <Menubar aria-label="Files">
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>
            <IconFilePlus />
            New file
            <MenubarShortcut>⌘N</MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            <IconFolderOpen />
            Open…
            <MenubarShortcut>⌘O</MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            <IconDownload />
            Export
          </MenubarItem>
          <MenubarSeparator />
          <MenubarItem variant="destructive">
            <IconTrash />
            Move to trash
          </MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>Edit</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>
            <IconScissors />
            Cut
            <MenubarShortcut>⌘X</MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            <IconCopy />
            Copy
            <MenubarShortcut>⌘C</MenubarShortcut>
          </MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}
```

### Disabled

Disable a single menu with `disabled` on `<MenubarMenu />`, or the whole bar on `<Menubar />`. Arrow keys skip disabled menus.

```tsx title="components/examples/menubar/disabled.tsx"
import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarTrigger,
} from "@/components/ui/menubar"

export function MenubarDisabled() {
  return (
    <div className="flex flex-col items-center gap-4">
      <Menubar aria-label="Document">
        <MenubarMenu>
          <MenubarTrigger>File</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Save</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
        <MenubarMenu disabled>
          <MenubarTrigger>Review</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Accept all changes</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
        <MenubarMenu>
          <MenubarTrigger>Help</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Keyboard shortcuts</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
      </Menubar>
      <Menubar aria-label="Locked document" disabled>
        <MenubarMenu>
          <MenubarTrigger>File</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Save</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
        <MenubarMenu>
          <MenubarTrigger>Edit</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Undo</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
      </Menubar>
    </div>
  )
}
```

### Vertical

`orientation="vertical"` stacks the menus and switches navigation to the up and down arrows. Open the menus to the side with `side="inline-end"`.

```tsx title="components/examples/menubar/vertical.tsx"
import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarTrigger,
} from "@/components/ui/menubar"

const menus = [
  { label: "Projects", items: ["All projects", "Starred", "Archived"] },
  { label: "Team", items: ["Members", "Roles", "Invitations"] },
  { label: "Billing", items: ["Plan", "Invoices", "Payment method"] },
]

export function MenubarVertical() {
  return (
    <Menubar aria-label="Workspace" orientation="vertical">
      {menus.map((menu) => (
        <MenubarMenu key={menu.label}>
          <MenubarTrigger>{menu.label}</MenubarTrigger>
          <MenubarContent side="inline-end" align="start" sideOffset={8}>
            {menu.items.map((item) => (
              <MenubarItem key={item}>{item}</MenubarItem>
            ))}
          </MenubarContent>
        </MenubarMenu>
      ))}
    </Menubar>
  )
}
```

### Right to left

Menus, submenus and arrow keys follow the reading direction. Shortcuts stay in keyboard order.

```tsx title="components/examples/menubar/rtl.tsx"
import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarShortcut,
  MenubarSub,
  MenubarSubContent,
  MenubarSubTrigger,
  MenubarTrigger,
} from "@/components/ui/menubar"

export function MenubarRtl() {
  return (
    <div dir="rtl">
      <Menubar aria-label="المحرر">
        <MenubarMenu>
          <MenubarTrigger>ملف</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>
              علامة تبويب جديدة
              <MenubarShortcut>⌘T</MenubarShortcut>
            </MenubarItem>
            <MenubarSub>
              <MenubarSubTrigger>مشاركة</MenubarSubTrigger>
              <MenubarSubContent>
                <MenubarItem>رابط البريد</MenubarItem>
              </MenubarSubContent>
            </MenubarSub>
          </MenubarContent>
        </MenubarMenu>
        <MenubarMenu>
          <MenubarTrigger>تحرير</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>تراجع</MenubarItem>
            <MenubarItem>إعادة</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
      </Menubar>
    </div>
  )
}
```

## Keyboard

| Key | Action |
| --- | --- |
| `Tab` | Moves focus into the menubar, then out of it. |
| `←` `→` | Moves between menus. With a menu open, opens the next one instead. |
| `Enter` `Space` `↓` | Opens the focused menu. |
| `↑` `↓` | Moves between items in an open menu. |
| `→` | Opens a submenu from its trigger. |
| `Esc` | Closes the open menu and returns focus to its trigger. |

## Accessibility

- Give the menubar a name with `aria-label`. It renders `role="menubar"` and each trigger is a menu item, so screen readers announce it as an application menu.
- The bar is a single tab stop. Arrow keys move between menus, so the page's tab order stays short.
- The sliding highlight is decoration. With reduced motion it just appears on the open menu.

## API reference

Menu parts accept the same props as their Dropdown menu counterparts.

### Menubar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` |  |
| `loopFocus` | `boolean` | `true` | Wrap from the last menu back to the first. |
| `modal` | `boolean` | `true` | Block page scrolling and outside clicks while a menu is open. |
| `disabled` | `boolean` | `false` |  |
| `render` | `ReactElement \| (props, state) => ReactElement` | `<div>` |  |

| Attribute | Description |
| --- | --- |
| `data-slot="menubar"` | Target the bar in CSS. |
| `data-orientation` | The orientation. |
| `data-has-submenu-open` | Present while any menu in the bar is open. |
| `data-slot="menubar-highlight"` | The sliding background behind the open trigger, with data-visible. |
| `--menubar-radius` | Corner radius of the bar. Triggers and the highlight derive theirs from it. |

### MenubarMenu

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | – |  |
| `defaultOpen` | `boolean` | `false` |  |
| `onOpenChange` | `(open: boolean, details) => void` | – |  |
| `disabled` | `boolean` | `false` |  |

### MenubarTrigger

| Attribute | Description |
| --- | --- |
| `data-slot="menubar-trigger"` | Target triggers in CSS. |
| `data-popup-open` | Present while its menu is open. |
| `data-disabled` | Present when disabled. |

### MenubarContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `"top" \| "bottom" \| "left" \| "right" \| "inline-start" \| "inline-end"` | `"bottom"` |  |
| `align` | `"start" \| "center" \| "end"` | `"start"` |  |
| `sideOffset` | `number` | `8` |  |
| `alignOffset` | `number` | `-4` | Lines the menu's items up with the trigger's label. |

| Attribute | Description |
| --- | --- |
| `data-slot="menubar-content"` | Target menus in CSS. |
| `data-instant="group"` | Present when the menu opened by sweeping from another one. It switches without animating. |

### Items

`MenubarItem`, `MenubarCheckboxItem`, `MenubarRadioGroup`, `MenubarRadioItem`, `MenubarGroup`, `MenubarLabel`, `MenubarSeparator`, `MenubarShortcut`, `MenubarSub`, `MenubarSubTrigger` and `MenubarSubContent` are the Dropdown menu parts with `menubar-*` slots. See the Dropdown menu API for their props.

## Notes for AI assistants

- Install a component with the shadcn CLI: `npx shadcn@latest add https://hextaui.com/r/<name>.json`. It adds the source, the HextaUI theme tokens and any HextaUI components it depends on. `https://hextaui.com/r/all.json` installs every component.
- The code is then owned by the project, like shadcn/ui. There is no HextaUI npm package. HextaUI is MIT licensed and free for personal and commercial use.
- Behavior and accessibility come from Base UI (`@base-ui/react`). Compose with the `render` prop, not `asChild`.
- Styling uses Tailwind CSS v4 with theme tokens. Merge classes with `cn` from the `cn` package.
- Icons come from `@tabler/icons-react`.
- Import components from `@/components/ui/<name>`, hooks from `@/hooks/<name>` and utilities from `@/lib/<name>`.

Every HextaUI doc: https://hextaui.com/llms.txt
