Menus
tier 2 — classes
One dropdown panel style, for a short list of actions opened from a control, and the
value trigger that opens one. The header's user menu is the same .menu class
— there is not a second panel style for the frame. A panel of content is a
popover.
role="menu"
That role promises arrow-key navigation
and a roving tabindex this does not implement; the items are ordinary links and buttons that
tab. Put aria-expanded on the trigger and leave the panel unlabelled by role.
Anchored under a trigger
Open it — the demo is live. .menu-anchor wraps the trigger and
the panel and declares the anchor-name the panel positions against, so the
panel lands under the trigger with nothing measured. data-menu-toggle on the
trigger hands it to sednaUi.menu — one panel open at a time, a click outside or
on an item closing it, Escape closing it and returning focus to the trigger — and
the closed state is the hidden attribute rather than a class,
which is what takes the panel out of the tab order as well as out of the layout; an app
holding the state in C# leaves the attribute off and renders hidden itself. The
panel lines up with the anchor's trailing edge and sizes to its widest row, floored
at 220px — add .menu--start to the panel for a trigger near the leading edge.
A menu is for actions: a list of values is a <select> or a
segmented control, a list of places is the sidebar.
<div style="padding-bottom:210px">
<div class="menu-anchor">
<button class="btn" type="button" data-menu-toggle aria-expanded="false">
<i class="ri-more-2-fill"></i> <span>Actions</span>
</button>
<div class="menu menu--start" hidden>
<span class="menu-label">This order</span>
<button class="menu-item" type="button">
<i class="ri-user-shared-line"></i> <span>Reassign</span><span class="menu-item-note">R</span>
</button>
<button class="menu-item" type="button">
<i class="ri-time-line"></i> <span>Snooze</span><span class="menu-item-note">S</span>
</button>
<button class="menu-item" type="button" aria-disabled="true">
<i class="ri-git-merge-line"></i> <span>Merge</span>
</button>
<hr class="menu-sep" />
<a class="menu-item" href="#"><i class="ri-external-link-line"></i> <span>Open in the warehouse</span></a>
<button class="menu-item menu-item--danger" type="button">
<i class="ri-delete-bin-line"></i> <span>Discard reservation</span>
</button>
</div>
</div>
</div>
At the pointer, with a submenu
Live — right-click a row. data-menu-context="<id>" on
any element opens the .menu with that id and the popover attribute
where the reader right-clicked, or below the element for the ContextMenu key and
Shift F10; the script places an invisible .menu-point
there and the panel flips at the screen's edges as a dropdown does. The element it opened on
carries data-menu-context-target while it is open. A
.menu-sub-anchor holds an item with data-menu-sub and the
.menu.menu-sub it opens beside it. In every menu, ↑ ↓
Home End move between items and → ← enter and
leave a submenu.
For a menu your own code decides to open, call
sednaUi.menu.openAt('order-menu', event.clientX, event.clientY, row) —
elements or ids — or ISednaUi.OpenMenuAtAsync from
C#. It answers whether a menu opened. A scroll that moves the element it is about closes it,
and one opened at a bare point closes on any scroll outside it.
| ORD-4182 | Northwind Retail | Shipped |
| ORD-4183 | Alex Fischer | Open |
<div style="padding-bottom:210px">
<table class="table">
<tbody>
<tr data-menu-context="order-menu" tabindex="0"><td>ORD-4182</td><td>Northwind Retail</td><td>Shipped</td></tr>
<tr data-menu-context="order-menu" tabindex="0"><td>ORD-4183</td><td>Alex Fischer</td><td>Open</td></tr>
</tbody>
</table>
<div class="menu" id="order-menu" popover>
<button class="menu-item" type="button"><i class="ri-file-copy-line"></i> <span>Copy order number</span></button>
<div class="menu-sub-anchor">
<button class="menu-item" type="button" data-menu-sub aria-haspopup="menu" aria-expanded="false">
<i class="ri-flag-line"></i> <span>Set status</span> <i class="ri-arrow-right-s-line"></i>
</button>
<div class="menu menu-sub">
<button class="menu-item" type="button">Open</button>
<button class="menu-item" type="button">Shipped</button>
<button class="menu-item" type="button">On hold</button>
</div>
</div>
<hr class="menu-sep" />
<button class="menu-item menu-item--danger" type="button"><i class="ri-delete-bin-line"></i> <span>Cancel order</span></button>
</div>
</div>
A value trigger in a table cell
Live — open one, then scroll the table sideways.
.value-trigger shows the current value of something and opens a menu to
change it — a status, an assignee, an access level. Reach for it where a
<select> would be wrong because the options need icons or a description;
where a plain list of values would do, use a <select>.
--value-trigger-measure is how much of the value the control keeps room for,
10ch by default, and .col-control on the th and the
td gives the column its floor — see Tables.
| Member | Access | Last seen |
|---|---|---|
| Jordan Weiss | 4 min ago | |
| Priya Nair | 2 days ago |
<div class="sedna-scroll-x" tabindex="0" role="group" aria-label="Repository access">
<table class="table">
<thead>
<tr>
<th>Member</th>
<th class="col-control">Access</th>
<th class="col-num">Last seen</th>
</tr>
</thead>
<tbody>
<tr>
<td>Jordan Weiss</td>
<td class="col-control">
<div class="menu-anchor">
<button class="value-trigger" type="button" data-menu-toggle aria-expanded="false"
aria-label="Access level for Jordan Weiss">
<span>Maintainer</span>
</button>
<div class="menu menu--start" hidden>
<span class="menu-label">Access level</span>
<button class="menu-item" type="button"><i class="ri-eye-line"></i> <span>Viewer</span></button>
<button class="menu-item" type="button"><i class="ri-git-pull-request-line"></i> <span>Contributor</span></button>
<button class="menu-item" type="button"><i class="ri-shield-star-line"></i> <span>Maintainer</span></button>
</div>
</div>
</td>
<td class="col-num col-muted">4 min ago</td>
</tr>
<tr>
<td>Priya Nair</td>
<td class="col-control">
<div class="menu-anchor">
<button class="value-trigger" type="button" data-menu-toggle aria-expanded="false"
aria-label="Access level for Priya Nair">
<span>Viewer</span>
</button>
<div class="menu menu--start" hidden>
<span class="menu-label">Access level</span>
<button class="menu-item" type="button"><i class="ri-eye-line"></i> <span>Viewer</span></button>
<button class="menu-item" type="button"><i class="ri-git-pull-request-line"></i> <span>Contributor</span></button>
<button class="menu-item" type="button"><i class="ri-shield-star-line"></i> <span>Maintainer</span></button>
</div>
</div>
</td>
<td class="col-num col-muted">2 days ago</td>
</tr>
</tbody>
</table>
</div>
Dismissing it
There is no JavaScript behind this. Put .menu-scrim immediately
before the panel — a transparent full-viewport element that catches the
click which closes the menu, and coming first in the markup is what puts the panel above it.
Handle Escape on the wrapper as well, or the menu can only be closed with a
pointer.
<div class="menu-anchor" @onkeydown="HandleKeyDown">
<button class="btn" type="button" aria-expanded="@_open" @onclick="Toggle">Actions</button>
@if (_open)
{
<div class="menu-scrim" @onclick="Close"></div>
<div class="menu">
<button class="menu-item" type="button">Reassign</button>
</div>
}
</div>