DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
BreadcrumbTabsPaginationStepperMenu ItemToolbarMenubarNavigation MenuAccordionCollapsibleCommandBottom NavTop Header MobileTop Header DesktopSidebar
AI agentsContributing
  1. Components
  2. Menu Item

Menu Item

Menu-styled rows that live on the page instead of in a popup — settings lists, account panels, sheet bodies and sidebars of actions. Plain or grouped cards, with icons, avatars, values, badges, shortcuts, checkbox, switch and radio rows.

$ pnpm add @tessinaui/ui

When to use

  • Rows that live on the page. A settings screen, an account panel, or a list of actions inside a sheet or card. Each row is its own Tab stop; the rows are the ones Dropdown Menu paints.
  • Settings that take effect at once. MenuItemSwitch for on and off, MenuItemCheckbox for several, MenuItemRadioGroup for one of many. Use variant="grouped" for the settings-screen layout.
  • Labels that name the result. Start an action with a verb ("Export", "Invite people") and a destination with a noun ("Billing"). Sentence case, no articles, no trailing period. End a label with "…" when the action needs more input before it happens ("Rename…").
  • intent="error" for an action that cannot be undone, placed last. Reversible actions, such as archive or remove from favourites, stay neutral. So does a link to a destructive page, such as "Delete account" in a settings nav: it is a destination, not the action.
  • One icon rule per group. Give every row in a group a leading icon, or none. When rows mix, use inset so the labels share one edge.
  • Hide what is never available; disable what will be. Hide an action the person will never be allowed to take. Disable one that will become available later: a disabled action row stays focusable so it can still be found. A checkbox, switch or radio row follows its control, so disabled takes it out of the Tab order; to keep one findable, make it readOnly and say why in its description.

When not to use

  • For a popup that opens from a trigger or a right-click. Use DropdownMenu or ContextMenu.
  • For a command palette or a choice from a list. Use Command or Select.
  • For a mobile bottom sheet of actions. Use ActionSheet, which has its own tile and cancel layouts.
  • For the app-shell navigation rail. Use Sidebar, which adds collapse and tooltips.
  • For a link, button or input inside a row's label or description. The row is already a button. Use action for a second control.
  • For nesting by indentation. Do not nest more than one level; use an expandable row or a new screen.

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
import {
  MenuItemList,
  MenuItemGroup,
  MenuItem,
  MenuItemSwitch,
} from "@tessinaui/ui/menu-item";
example.tsx
<MenuItemList variant="grouped">
  <MenuItemGroup label="General">
    <MenuItem leadingIcon={<Palette />} meta="System" chevron>
      Appearance
    </MenuItem>
    <MenuItemSwitch leadingIcon={<Bell />} checked={on} onCheckedChange={setOn}>
      Notifications
    </MenuItemSwitch>
  </MenuItemGroup>
  <MenuItemGroup>
    <MenuItem intent="error" leadingIcon={<LogOut />}>Sign out</MenuItem>
  </MenuItemGroup>
</MenuItemList>

The rows are the same rows Dropdown Menu paints — same sizes, radius steps, inks and destructive treatment — but each one is its own Tab stop on the page. A popup menu stays DropdownMenu.

Choosing between look-alikes

  • MenuItem — rows on the page: a settings screen, an account panel, a list of actions inside a sheet or card.
  • DropdownMenu / ContextMenu — the same rows in a popup that opens from a trigger or a right-click.
  • ActionSheet — a mobile bottom sheet of actions, with its own tile and cancel layouts.
  • Sidebar — the app-shell navigation rail, with collapse and tooltips.

Examples

Default

A list of actions with leading icons, a separator and a destructive sign-out, next to a text-only list.

Sizes

Five sizes from xs to xl. On a touch pointer every size keeps a 44px row.

Rounded

rounded sets the row radius — the active row's fill shows the corner. Content inside a row can follow it with rounded-[var(--menu-item-radius)].

Leading media

Avatars, logos, tinted tiles and a coloured glyph in the leading slot. menuItemAvatarSize picks the avatar that fits each row size, and inset lines plain rows up with a checkbox column.

Trailing content

A current value (meta) with a drill-in chevron, a count as a pill or a bare number, a trailing icon, and a shortcut beside a chevron.

Badges and shortcuts

badge sits right after the label; a badge passed as meta sits at the trailing edge. Shortcuts take canonical key names that resolve per platform.

Description and wrapping

Descriptions go under the label or beside it with descriptionPlacement="inline". Long content wraps by default, so nothing is cut off on a narrow screen. wrap={false} cuts it to one line with nothing to show the rest — keep it for text that is readable somewhere else too.

Destructive

intent="error" paints the row red and adds "destructive action" to what a screen reader hears. Put it last, after a separator or in its own group; destructiveHint rewords the hint. An active destructive row fills red at rest instead of taking the neutral active fill, so its red label never sits on grey.

States

Hover, keyboard focus and press; a disabled row that stays discoverable; and a loading row whose spinner replaces its icon.

Links and the current page

render={<a href />} makes the row a real link. active marks the current page with aria-current="page", and target="_blank" adds the outbound glyph and "(opens in a new tab)".

Checkbox rows

MenuItemCheckbox for multi-select, including a mixed "Show all" parent.

Radio rows

MenuItemRadioGroup for a single choice — a tick on the chosen row, or a radio on every row with indicator="radio". The group is one Tab stop and the arrow keys select.

Switch rows

MenuItemSwitch for settings that take effect at once.

Groups and separators

A group heading names its list and can carry an action; a footnote sits under the group. Rules between rows are none, full or inset.

Grouped cards

variant="grouped" puts each group in its own card with inset rules — the settings-screen layout.

Row action

action puts a second control beside the row. It is always visible on touch; with a mouse it appears on hover or when anything in the row has focus.

Expandable row

A row that expands in place, with aria-expanded and aria-controls — the phone-friendly alternative to a submenu.

Skeleton

MenuItemListSkeleton stands in for rows that have not loaded, built from the same size and radius maps as the real rows.

RTL

Under dir="rtl" the padding, trailing columns and chevron all mirror.

On color

Inside a coloured <Surface> a plain list inherits tone="on-color": its rows, rules, headings and footnotes take the plate's ink and muted tier, a row hovers and presses to a hairline instead of a page wash, the current row is the inverse chip, and a destructive row keeps its meaning through the plate's error ink. Nothing needs to be set.

A grouped card is the menu popup's own surface, so it keeps its page look on any plate: the rows, switches, badges and keycaps inside it read the card, while the group headings and footnotes between the cards read the plate.

API Reference

MenuItemList

PropTypeDefaultDescription
size"xs" | "sm" | "md" | "lg" | "xl""md"Row density for every row inside
rounded"none" | "sm" | "md" | "lg" | "full"—Row radius, and a grouped card's radius. Unset, both follow size
variant"plain" | "grouped""plain"Rows on the page, or each group in its own card
separators"none" | "full" | "inset""none" plain, "inset" groupedRules between rows
destructiveHintstring | false"destructive action"What a screen reader adds after a destructive row's name

MenuItemGroup

PropTypeDefaultDescription
labelReactNode—Section heading; names the group's list
actionReactNode—A control beside the heading
descriptionReactNode—Footnote under the group
listClassNamestring—Classes for the group's own list

MenuItem

PropTypeDefaultDescription
leadingIconReactNode—Icon, avatar, logo or tile before the label
trailingIconReactNode—Decorative icon at the end
descriptionReactNode—Secondary text
descriptionPlacement"block" | "inline""block"Under the label or beside it
wrapbooleantrueWrap long content; false truncates to one line
badgeReactNode—Status pill after the label
metaReactNode—Trailing value or count
shortcutstring | string[]—Keyboard hint; ["mod", "K"] resolves per platform
chevronbooleanfalseDrill-in chevron, mirrored in RTL
intent"none" | "error""none"error for a destructive action
insetbooleanfalseAlign the label with the checkbox column
activebooleanfalseCurrent destination — fill + aria-current="page"; a destructive row fills red
disabledbooleanfalseUnavailable; stays focusable
loadingbooleanfalseBusy; spinner replaces the leading icon
actionReactNode—A second control beside the row
renderReactElement—Replace the <button> — <a href>, a router link
size / rounded—from the listPer-row override

MenuItemCheckbox · MenuItemSwitch

Every display prop of MenuItem except inset, plus the Base UI control's own: checked, defaultChecked, onCheckedChange, disabled, readOnly, name, and indeterminate on the checkbox.

MenuItemRadioGroup · MenuItemRadio

The group takes value, defaultValue, onValueChange, disabled and name; name it with aria-label or aria-labelledby. Each radio takes value, the display props, and indicator: "check" | "radio" (default "check").

MenuItemListSkeleton

PropTypeDefaultDescription
itemsnumber4Row placeholders
media"none" | "icon" | "avatar""icon"What leads each row
showDescriptionbooleanfalseA second line per row
showMetabooleanfalseA trailing value placeholder
size / rounded / variant—as MenuItemListMatch the list it stands in for
labelstring"Loading", Arabic under dir="rtl"Announced while the placeholder is up

Accessibility

  • Semantics are a plain list — ul[role=list] > li > button (or a). There are no menu roles, because there is no menu.
  • A group's heading names its list. A separator is decorative.
  • A disabled action row keeps focus (aria-disabled) so it can still be found. Checkbox, switch and radio rows are Base UI's own controls rendered as the row, so their roles, aria-checked and keys come from the primitive — disabled takes them out of the Tab order, as it does the standalone controls, and readOnly keeps one focusable while refusing the toggle.

Keyboard

KeyAction
Tab / Shift+TabMove between rows, row actions and radio groups
Enter / SpaceActivate the row, or toggle a checkbox or switch row
ArrowDown / ArrowUpInside a radio group, move and select
StepperToolbar

On this page

When to useWhen not to useInstallationUsageChoosing between look-alikesExamplesDefaultSizesRoundedLeading mediaTrailing contentBadges and shortcutsDescription and wrappingDestructiveStatesLinks and the current pageCheckbox rowsRadio rowsSwitch rowsGroups and separatorsGrouped cardsRow actionExpandable rowSkeletonRTLOn colorAPI ReferenceMenuItemListMenuItemGroupMenuItemMenuItemCheckbox · MenuItemSwitchMenuItemRadioGroup · MenuItemRadioMenuItemListSkeletonAccessibilityKeyboard