Skip to the page
TACTILE UI

Kit part · Dropdowns & menus

Context menu

The menu’s own card, opened where you pointed: by right-click, by a long press on a touch screen, or from the keyboard with Shift F10 or the Menu key. The thing it opened on stays sunk while it is out.

Element
[data-menu-context]
Needs
kit.css + kit.mjs
Rules
context-menu.rules.md · 5
Finishes
Aluminium · Graphite · Porcelain · Black
Volumes
Tactile · Quiet
On this page
  1. Variants
  2. Every state, at once
  3. API
  4. Markup
  5. Accessibility
  6. How it’s built
  7. Rules
  8. Related parts
01

Variants

01Score.wav3.4 MB
A row · at rest
01Score.wav3.4 MB
The target while its card is out
Right-click
A tray, at the pointer
The card · actions and an option
The card · a danger line
02

Every state, at once

Both finishes, side by side. Hover, pressed and focus are forced with .is-hover, .is-down and .is-focus; checked and disabled are real.

Aluminium

PartRestHoverLong press heldCard outFocus
Target
Score.wav
Score.wav
Score.wav
Score.wav
Score.wav

Graphite

PartRestHoverLong press heldCard outFocus
Target
Score.wav
Score.wav
Score.wav
Score.wav
Score.wav
03

API

NameKindValuesWhat it does
.tui-contextclasson the targetA target that has a menu: the context-menu cursor, a hairline on hover, and the sunk look while pressed or open.
data-menu-contextattributea menu idOn the target: names the .tui-menu it opens. Several targets can share one menu.
tabindex="0" · role="group" · aria-labelattributeon the targetSo the keyboard reaches it. Say “Context menu: Shift F10” in the label.
aria-haspopup="menu" · aria-keyshortcutsattribute"Shift+F10"Tells assistive technology a menu is available, and how to reach it.
data-context-openattributeset by scriptPresent while the target’s card is out. The target stays down.
data-pressingattributeset by scriptPresent on a touch held towards the long press: the target sinks over the half second.
.tui-context__hintclass—A printed line that says how: “Right-click · long-press”.
.tui-menuclasspopover role="menu"The card itself, with all of the menu part’s lines: actions, options, separators, danger, submenus.
LONG_PRESS_MS · LONG_PRESS_SLOPconstant500 · 10How long a touch is held, and how far it may drift, to be a long press.
pressDrifted(from, to, slop)functionkit.mjsWhether a touch has moved too far to still be a press. Pure.
Right-click · long presspointer—Opens at the pointer with focus on the card; a scroll or a lift before half a second cancels the press.
Shift F10 · Menu keykeyboardon the targetOpens at the target with the first line focused. Escape or a choice returns focus to the target.
04

Markup

<div class="tui-context" tabindex="0" role="group"
     data-menu-context="file-menu" aria-haspopup="menu" aria-keyshortcuts="Shift+F10"
     aria-label="Score.wav, 3.4 MB. Context menu: Shift F10.">
  Score.wav
</div>

<!-- The card is the menu part's own -->
<div class="tui-menu" id="file-menu" popover role="menu" aria-label="Score.wav">
  <button role="menuitem" type="button" tabindex="-1">Open<kbd>Enter</kbd></button>
  <button role="menuitem" type="button" tabindex="-1">Rename<kbd>F2</kbd></button>
  <div role="separator"></div>
  <button class="tui-menu__item--danger" role="menuitem" type="button" tabindex="-1">Delete<kbd>Del</kbd></button>
</div>
import '/kit/kit.mjs';   // right-click, Shift+F10, the Menu key and the long press

// The lines act as any menu's do: aria-checked is already updated when this runs.
document.getElementById('file-menu').addEventListener('click', (e) => {
  const line = e.target.closest('[role^="menuitem"]');
  if (line) run(line.textContent.trim());
});

Needs kit.css and kit.mjs. How to install the kit.

05

Accessibility

  • The same actions must be reachable without the gesture: a context menu is a shortcut, never the only door. Put them in a visible key or a toolbar too.
  • The target is focusable and its label says how to open the menu; aria-keyshortcuts and aria-haspopup="menu" say it to assistive technology.
  • Shift F10 and the Menu key open the card at the target with its first line focused. Escape, or choosing a line, puts focus back on the target.
  • The long press is for touch only; it is cancelled by a scroll, so it never fights the page, and it does not also fire the target’s click.
  • The card is the menu part’s, so it has the same roles, arrow keys, type-ahead and states, and disabled lines stay in place.
  • The sunk look while the card is out is a second cue for which target the menu belongs to.
06

How it’s built

The card and the whole keyboard are the menu’s: menu.mjs already opens any data-menu-context target from right-click, Shift F10 and the Menu key, and returns focus to it. context-menu.mjs adds the gesture a touch screen lacks. A pointer that is not a mouse starts a half-second timer on the target, which sinks into its recess as time passes; a move past 10px, a lift or a scroll clears it; if it runs out, the menu opens at the finger and the lift that follows is not taken as a click. Android reports the same press as a contextmenu event, which the module quietly absorbs so the card opens once.

07

Rules

context-menu.rules.mdIn the brain

A shortcut for people who know it is there: the same actions must live somewhere you can see.

5 rules in this file, with the foundations they rest on and the anti-patterns that break them. Full rules ship in the brain with the Pass.

08

Menu

A card of actions and options that slides out from under its key. The key stays down while the card is out; an option that is on lights its lamp.

Reserve

Kept for a power cut.

Popover

A card hung from the key that opened it, with a printed leader running back to the key. Not modal: the page stays live around it.

2 matches
BatteryGB
Set battery reserve

Command palette

Every action and page behind one key: a modal plate whose entry line is a lit display, with the results printed below and the chosen one pressed in.

Docs
Finish
Volume