Accordion navigation for in-page sidebars such as settings pages. Each group is a header button over a list of rows; one group is open at a time unless multiple is set. The active row carries the accent bar and is data, so the host page owns routing. The whole is a nav landmark named by the host's aria-label.
Group headers and rows are ordinary tab stops in document order: this is navigation, not a composite widget, so nothing leaves the tab order. The arrow keys are a shortcut from header to header, past a whole group's rows. A header carries aria-expanded and aria-controls, and the active row carries aria-current="page".
// setup(root): properties and events
setup(root) {
const el = root.querySelector('#pm-multi');
el.groups = settingsGroups();
el.expanded = [0, 2];
}
Row states
A row marked active carries the orange bar and the navy-soft fill. A disabled row renders a native disabled button, so a destination gated by plan or permission keeps its place in the list.
A row with href is a real link in the same place: Enter follows it and suiPick still fires on the click. A disabled row stays a disabled button whatever its href says.
A header toggle emits suiExpandedChange with the new array of expanded indexes, as sui-accordion does. Choosing a row emits suiPick with the item, as every menu does, and then runs its command.
// setup(root): properties and events
setup(root) {
const log = root.querySelector('#pm-log');
const el = root.querySelector('#pm-events');
const groups = settingsGroups();
groups[0].items[0].command = () => {
log.textContent += ' Its command ran.';
};
el.groups = groups;
el.addEventListener('suiExpandedChange', (e) => {
log.textContent = e.detail.length ? `Open groups [${e.detail.join(', ')}].` : 'Every group is closed.';
});
el.addEventListener('suiPick', (e) => {
log.textContent = `Picked ${e.detail.label}.`;
});
}
Keyboard
Key
Action
Tab
Moves through the headers and the rows of open groups, in document order.
ArrowDown
From a header, moves to the next header, wrapping past the last.
ArrowUp
From a header, moves to the previous header, wrapping past the first.
HomeEnd
From a header, moves to the first or the last header.
EnterSpace
Opens or closes the focused group, or follows the focused row.
API
<sui-panel-menu>
Properties
Property
Attribute
Type
Default
Description
expanded
property only
number[]
[0]
Indexes of the expanded groups. Mutable; the element updates it as headers toggle, and opens the group that holds an active row when groups arrives with one in a closed group. It is not the boolean open a dismissible surface carries, which is why it has its own name. Property, not attribute.
groups
property only
SuiPanelMenuGroup[]
[]
Groups to render: { label, icon?, items: [{ label, active?, badge?, disabled?, command?, href?, target? }] }[]. Headers and rows are plain buttons in document order, so Tab reaches every one; the arrow keys are a shortcut between headers and take nothing out of the tab order. A row with href is a real link in the same place. Property, not attribute.
multiple
multiple
boolean
false
Allow more than one group open at once. Default single-open, as sui-accordion. Reflected.
width
width
number
250
Card width in px.
Events
Event
Detail
React
Vue
Description
suiExpandedChange
number[]
onSuiExpandedChange
@sui-expanded-change
Fires after a group header toggles; detail is the new expanded list of open group indexes. Indexed expansion is not open state, so this is not the suiOpen and suiClose pair, as in sui-accordion. Does not bubble.
suiPick
SuiPanelMenuItem
onSuiPick
@sui-pick
Fires when a row is chosen; detail is the item object, as every menu reports a pick. item.command() also runs.
Methods
Method
Signature
Description
setFocus()
setFocus() => Promise<void>
Moves keyboard focus to the first group header.
Types
SuiPanelMenuGroupinterfaceOne accordion group: a header button plus its rows.
label
string
icon?
string
items
SuiPanelMenuItem[]
SuiPanelMenuIteminterfaceOne row inside a PanelMenu group.
label
string
active?
boolean
badge?
string | number
disabled?
boolean
Inert row (a destination gated by permission or plan). Renders a native disabled button.
command?
() => void
Runs when the row is chosen, alongside suiPick (the family's item-command rule).
href?
string
Renders the row as a real link: Enter follows it, and suiPick and command still run on the click. A disabled row stays a disabled button whatever href says.
target?
string
Link target for a row with href, e.g. _blank. A _blank row gets rel="noopener".
Theming
Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.