Skip to content

PlMenubar

The strip of words at the top of an application (File, Edit, View) each of which opens a menu. It is one thing to a screen reader, and only ever one of its menus is open.

React
tsx
import { PlMenubar, PlMenubarMenu, PlMenuItem } from 'plass-ui';

<PlMenubar>
  <PlMenubarMenu label="File">
    <PlMenuItem shortcut="Mod+N">New</PlMenuItem>
  </PlMenubarMenu>
</PlMenubar>;
dart
import 'package:plass_ui/plass_ui.dart';

PlMenubar(
  menus: <PlMenubarMenu>[
    PlMenubarMenu(
      label: 'File',
      items: <PlMenuEntry>[PlMenuItem(label: 'New', shortcut: '⌘N')],
    ),
  ],
);

Props

PropTypeDefaultDescription
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The strip's height and type scale — a rung below the control ladder at every step
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. It reaches the hover, the open menu and the focus rings; the bar draws nothing
densityshared'default' | 'compact''default'The padding beside each word. Even the default uses the compact track: a strip is not a row of buttons
orientationshared'horizontal' | 'vertical''horizontal'Which way the bar runs. The arrow keys follow it
modalbooleantrueWhether an open menu takes the page away. On, an open menu is what the pointer is talking to
loopFocusbooleantrueWhether the arrow keys wrap around at the ends of the bar
disabledbooleanfalseDisables every menu on the bar at once
childrenReactNodeThe menus
PropTypeDefaultDescription
menus * List<PlMenubarMenu>The menus — data here rather than composed children
orientationsharedPlassOrientationPlassOrientation.horizontalWhich way the bar runs
disabledboolfalseDisables every menu on the bar at once
sizesharedPlassSizePlassSize.mdThe strip's height and type scale — a rung below the control ladder at every step
colorsharedPlassColorPlassColor.primarySemantic colour role. It reaches the hover, the open menu and the focus rings; the bar draws nothing
densitysharedPlassDensityPlassDensity.standardThe padding beside each word. Even the default uses the compact track: a strip is not a row of buttons
semanticLabelString?The name a screen reader gives the bar

Every native <div> attribute passes straight through. color is excluded because it is a Plass prop here.

PlMenubarMenu

PropTypeDefaultDescription
label * ReactNodeThe word on the bar
startIconReactNodeContent before the label
disabledbooleanfalseUnavailable. The word stays on the bar and opens nothing
childrenReactNodeThe rows, written exactly as they are inside a PlMenu
classNamestringClasses on the word this menu is opened by, alongside the component's own rather than in place of them
styleCSSPropertiesInline styles on the word this menu is opened by, applied over the custom properties it wrote
PropTypeDefaultDescription
label * StringThe word on the bar
items * List<PlMenuEntry>The rows, written exactly as they are inside a PlMenu
startIconWidget?Content before the label
disabledboolfalseUnavailable. The word stays on the bar and opens nothing

What the shared axes mean across the library is in prop conventions.

Composition

Not the look. A row of separate PlMenus would look the same and differ in the ways that matter: it would be a row of buttons to a screen reader rather than one menubar whose words are menuitems, and nothing would keep two of them from being open at once.

On a bar, crossing the strip also walks through the menus and the arrow keys move between them as well as inside them. Base UI owns all of it.

Shared with PlMenu

A PlMenubarMenu takes the rows a PlMenu takes (PlMenuItem, PlMenuSeparator, PlMenuGroup, PlMenuSubmenu, PlMenuCheckboxItem, PlMenuRadioItem), because it is the same menu with a different trigger.

What it does not take is size, color or density. Those belong to the bar: they are the one place the axes can be set once and hold for every menu on the strip, and a bar whose third menu is a size out is not a bar.

React

Examples

size

The strip sits a rung below the control ladder at every step, and it takes the compact padding track even at density="default".

Both are the same decision: a menu bar is a strip of words, and it is usually inside something that already has a height, a PlToolbar, a PlHeader. Sized as controls, File Edit View would be three buttons in a row and would make the bar taller than the thing it is drawn on.

React

orientation

vertical is the shape a side rail of menus takes.

React

No surface

A menu bar sits on something, and a sheet under a strip that is already on a sheet is two sheets. The bar contributes a flex row and four colour slots, and nothing else.

The open menu is marked in colour and nothing else. The word does not move and the strip does not change height, which is the same rule every control in the library follows under a pointer.

Differences from the React build

ReactFlutterWhy
composed PlMenubarMenu childrenmenus: List<PlMenubarMenu> as dataA menu on a bar is a word and a list of rows, and a list is what the strip can count.
crossing the strip walks through the menuspressing elsewhere puts the open one awayAn open menu's dismiss layer is between the pointer and the strip, so the words never hear it arrive. Only one is ever open either way.
modal, loopFocusThere is no page to make inert and no arrow-key ring to wrap: an open menu already owns the pointer.
aria-expanded on the wordSemanticsRole.menuItem with expandedThe same state under the framework's own name. Once a menu is open it is the accessibility tree, so the strip says which one in colour as well.
className, style, native attributesThere is no class list and no style attribute to pass through.

Accessibility

  • The strip is a real menubar and each word a menuitem that reports whether it is expanded.
  • The focus ring is turned inward on a word, because a strip's items are a hair apart and a ring drawn outside one would overlap its neighbours.
  • A disabled menu keeps its word on the bar and opens nothing. disabled on the bar does it to every menu at once.
  • The arrow keys move along the bar and into an open menu; Esc closes it and returns focus to its word. loopFocus decides whether the bar wraps at its ends.
  • modal is on by default, so an open menu is what the pointer is talking to. The page behind it is inert until it closes.

Released under the MIT License