Skip to content
LuoForge/Tungsten

Menu

Install

Add the package and import the component.

pnpm add @hey-mike/tungsten
pnpm add @hey-mike/tungsten
import { Menu } from '@hey-mike/tungsten';
import { Menu } from '@hey-mike/tungsten';

Preview

Same fixtures used by the visual-regression suite.

Usage

apps/docs/app/snapshots/menu/page.tsx

import { VariantGrid } from '../_components/VariantGrid';
import { EditMenu, ViewMenu } from './_examples';

// Menus render CLOSED here (resting trigger, click-to-open) to match the
// component-gallery convention used by Radix/MUI/Ant/shadcn. This is the
// fixture the docs preview iframe shows. The OPEN state — which visual
// regression must cover (items, checkbox, radio, submenu, destructive) — lives
// at the test-only /snapshots/menu/open route (see ./open/page.tsx).
export default function MenuSnapshot() {
  return (
    <VariantGrid
      title="Menu"
      variants={[
        { label: 'ghost trigger', node: <EditMenu /> },
        { label: 'checkbox + radio + sub', node: <ViewMenu /> },
      ]}
    />
  );
}
import { VariantGrid } from '../_components/VariantGrid';
import { EditMenu, ViewMenu } from './_examples';

// Menus render CLOSED here (resting trigger, click-to-open) to match the
// component-gallery convention used by Radix/MUI/Ant/shadcn. This is the
// fixture the docs preview iframe shows. The OPEN state — which visual
// regression must cover (items, checkbox, radio, submenu, destructive) — lives
// at the test-only /snapshots/menu/open route (see ./open/page.tsx).
export default function MenuSnapshot() {
  return (
    <VariantGrid
      title="Menu"
      variants={[
        { label: 'ghost trigger', node: <EditMenu /> },
        { label: 'checkbox + radio + sub', node: <ViewMenu /> },
      ]}
    />
  );
}

Props

Surface specific to <Menu />.

PropTypeDefaultDescription
classNamestring
defaultOpenbooleanfalse
openboolean
onOpenChange((open: boolean) => void)
modalbooleanWhen false, the menu is non-modal: outside scroll/pointer interaction stays enabled and sibling content is not hidden from assistive tech. Omit to keep Radix's default (modal).

Sub-components

Composition slots re-exported from the same module.

MenuContent

PropTypeDefaultDescription
align"start" | "center" | "end"
side"top" | "right" | "bottom" | "left"
sideOffsetnumber4

MenuRadioGroup

PropTypeDefaultDescription
classNamestring
valuestring
onValueChange((value: string) => void)

MenuSub

PropTypeDefaultDescription
defaultOpenboolean
openboolean
onOpenChange((open: boolean) => void)

MenuSubContent

PropTypeDefaultDescription
sideOffsetnumber0

MenuSeparator

PropTypeDefaultDescription
classNamestring

MenuLabel

PropTypeDefaultDescription
classNamestring
idstringOptional element id. When a MenuLabel is rendered inside a MenuGroup the group auto-wires its aria-labelledby to this id, so the common case needs no manual wiring; supply id only to name the label explicitly.

MenuGroup

PropTypeDefaultDescription
classNamestring
aria-labelledbystringIDREF naming the group for assistive tech. If omitted and the group contains a MenuLabel, the label is auto-assigned a generated id and this is wired to it automatically.

MenuTrigger

PropTypeDefaultDescription
asChildbooleanRender through to the child element (Radix slot) instead of a <button>. Note: the ref type stays HTMLButtonElement even when the child is another element (e.g. an <a>) — cast at the call site if you need the exact type.

MenuItem

PropTypeDefaultDescription
classNamestring
disabledboolean
iconReactElement<unknown, string | JSXElementConstructor<any>>
shortcutstring
destructiveboolean
hrefstring
onClick(() => void)

MenuCheckboxItem

PropTypeDefaultDescription
classNamestring
checked*boolean
onCheckedChange((checked: boolean) => void)
disabledboolean
shortcutstring

MenuRadioItem

PropTypeDefaultDescription
classNamestring
value*string
disabledboolean
shortcutstring

Source