Commands and contextual actions
Connect discoverable commands to actual object changes, retained state and recoverable outcomes.
Match access to task scope
| Surface | Use |
|---|---|
| Direct Button / Toolbar | Frequent visible action; explicit target and outcome |
| Menu | Compact command/settings list associated with a visible trigger |
| Context menu plus visible alternative | Object-specific commands with equivalent keyboard/single-pointer reachability |
| Menubar | Persistent application commands with coordinated menus |
| Select / Radio / Checkbox | Form values and independent selection, rather than commands |
| Dialog / confirmation / undo | Consequential decision, input, pending result or independent recovery |
Working command state and recovery
Local reading record
English-only local notes are visible.
Local copies: 0.
No local command run. Reading layout: compact. Menus do not prove authority or permission.
'use client';
import { useRef, useState } from 'react';
import { Button, Menu, MenuCheckboxItem, MenuCheckboxItemIndicator, MenuGroup, MenuGroupLabel, MenuItem, MenuPopup, MenuPortal, MenuPositioner, MenuRadioGroup, MenuRadioItem, MenuRadioItemIndicator, MenuSeparator, MenuSubmenuRoot, MenuSubmenuTrigger, MenuTrigger } from '@mobtranslate/ui';
export function MenuCommandsExample() {
const [open, setOpen] = useState(false);
const [title, setTitle] = useState('Local reading record');
const [copies, setCopies] = useState(0);
const [notes, setNotes] = useState(true);
const [layout, setLayout] = useState('compact');
const [pending, setPending] = useState(false);
const [result, setResult] = useState('No local command run.');
const context = useRef<HTMLDivElement>(null);
const submenu = useRef<HTMLElement>(null);
function resolve(outcome: 'complete' | 'fail' | 'cancel') {
if (!pending) return;
if (outcome === 'complete') { setTitle('Updated local reading record'); setResult('Local title changed. No server write occurred.'); }
if (outcome === 'fail') setResult('Local rename failed. The original title remains; retry is available.');
if (outcome === 'cancel') setResult('Local rename cancelled. The original title remains.');
setPending(false); context.current?.focus();
}
return <div data-menu-lab className="space-y-4">
<div ref={context} tabIndex={-1} role="region" aria-label="Local command context" data-menu-record style={{lineHeight:layout === 'compact' ? 1.5 : 1.75, padding:layout === 'compact' ? 8 : 16}}>
<p>{title}</p>{notes && <p data-command-notes>English-only local notes are visible.</p>}<p data-command-copies>Local copies: {copies}.</p>
</div>
<Menu open={open} onOpenChange={setOpen} disabled={pending}>
<MenuTrigger data-command-trigger render={<Button variant="outline" disabled={pending}>Local record commands</Button>} />
<MenuPortal><MenuPositioner sideOffset={8} align="start"><MenuPopup aria-label="Local record commands" finalFocus={context}>
<MenuGroup><MenuGroupLabel>Local commands</MenuGroupLabel>
<MenuItem onClick={() => { setCopies(count => count+1); setResult('Local copy added. No source was duplicated remotely.'); }}>Duplicate local record</MenuItem>
<MenuItem onClick={() => { setPending(true); setResult('Local rename pending. Choose an explicit outcome below.'); }}>Rename local record…</MenuItem>
<MenuItem disabled onClick={() => setResult('Unexpected unavailable command activation.')}>Publish to server (unavailable)</MenuItem>
</MenuGroup><MenuSeparator />
<MenuCheckboxItem checked={notes} onCheckedChange={setNotes} className={state => state.checked ? 'local-notes-checked' : undefined}><MenuCheckboxItemIndicator keepMounted aria-hidden="true">✓</MenuCheckboxItemIndicator>Show local notes</MenuCheckboxItem>
<MenuSubmenuRoot closeParentOnEsc={false} onOpenChangeComplete={childOpen => { if (!childOpen && open) submenu.current?.focus(); }}><MenuSubmenuTrigger ref={submenu} label="Reading layout">Reading layout <span aria-hidden="true">›</span></MenuSubmenuTrigger>
<MenuPortal><MenuPositioner side="right" align="start" sideOffset={4}><MenuPopup aria-label="Reading layout choices" finalFocus={submenu}>
<MenuRadioGroup value={layout} onValueChange={setLayout} aria-label="Local reading density">
<MenuRadioItem value="compact"><MenuRadioItemIndicator keepMounted aria-hidden="true">●</MenuRadioItemIndicator>Compact layout</MenuRadioItem>
<MenuRadioItem value="comfortable"><MenuRadioItemIndicator keepMounted aria-hidden="true">●</MenuRadioItemIndicator>Comfortable layout</MenuRadioItem>
</MenuRadioGroup>
</MenuPopup></MenuPositioner></MenuPortal>
</MenuSubmenuRoot>
</MenuPopup></MenuPositioner></MenuPortal>
</Menu>
{pending && <div className="flex flex-wrap gap-3"><Button type="button" onClick={() => resolve('complete')}>Complete local rename</Button><Button type="button" variant="outline" onClick={() => resolve('fail')}>Fail local rename</Button><Button type="button" variant="outline" onClick={() => resolve('cancel')}>Cancel local rename</Button></div>}
<Button type="button" variant="outline" disabled={pending} onClick={() => { setTitle('Local reading record'); setCopies(0); setResult('Restored the local record and cleared local copies.'); }}>Restore local command context</Button>
<p role="status">{result} Reading layout: {layout}. Menus do not prove authority or permission.</p>
</div>;
}
Implement a complete command boundary
- Name the object and action. A detached/contextual trigger must still identify which current record it operates on.
- Execute a real synchronous effect or snapshot a requested async operation. Close the menu according to the command, retain the old data and provide pending/recovery controls outside it.
- Prevent duplicate operations, handle field/general failures and distinguish cancellation of a local wait from confirmed remote cancellation. Preserve operation/record identity against stale responses.
- Expose the confirmed retained result and a surviving focus target. When removal disables/unmounts the trigger, independent restore or the next task context must remain reachable.
- Menu settings need checked semantics and visible marks. Radio alternatives operate on one actual group; commands do not become form values just because they look like choices.
Accessibility and adaptive review
- Check names, roles, expanded/checked/disabled states, keyboard and focus transitions against the installed version. Verify actual pointer and keyboard effects rather than only callbacks.
- Keep touch targets reachable, menus bounded and last actions scrollable at narrow widths/zoom. Verify long press on hardware and retain a visible alternative.
- Portals can escape local themes and containment. Check actual popup colors, focus cues, collision placement and transformed/scrolling ancestors.
- A command result must preserve source, review and permission evidence independently. A successful local action does not establish cultural authority.
Acceptance and failure review
| Check | Observable result |
|---|---|
| Open / navigate / dismiss | Named list, usable keyboard/pointer movement and surviving restored focus |
| Unavailable | Actual suppression with a useful explanation; no hidden operation |
| Settings / submenu | Actual rendered state, accurate checked semantics and a return path |
| Pending / failed / cancelled | Retained context, duplicate guard, truthful result and retry/recovery |
| Completed / restored | Confirmed data effect or clearly named local simulation; independent recovery remains usable |
Verification and release scope
- Verify the actual command effect, requested/retained state, recovery and focus after activation. A closed menu or click callback is not a save receipt.
- Check trigger naming/expanded state, item roles, group names, checked states, disabled enforcement, keyboard entry/exit, type navigation, submenu return and focus restoration. Inspect the exact installed behavior rather than assuming an upstream convention.
- Check both themes, readable text/focus cues, long labels, bounded scrolling, 320px reflow, reduced motion, forced colors, zoom and the actual touch device. Context long press is not proven by mouse tests.
- Use the checked example sources and the menu-family browser suite, then a named screen-reader/browser pair for spoken output. These local English-only examples prove no server write, permission, source authority or qualified review.