Components/Context Menu

Context Menu

Thirty-four variants across five components. Four menus, twenty-eight rows, a submenu trigger, a heading and a rule.

ContextMenu / Item · Components frameLive in the browser · drawn to match the file
Variant
State
Show Icon
Context Menu Text ⇧⌘P
Variant=Shortcut · State=Default · Show Icon=Yes300 × 28 in the file

Radio reuses the Show Icon axis for its tick rather than for a leading glyph, which is why the control above renames itself. A radio row also carries a fixed 20px spacer at the end, so a checked and an unchecked option line up down the right-hand edge.

Four menus

Light or dark, solid or see-through

280 by 360 at radius 10, a 1px border and two stacked shadows. Colour swaps the surface between white and #262626 and takes the ink with it; Appearance drops that surface to 70%. Hover a row below: the highlight follows the menu rather than fighting it.

ContextMenu · Components frameLive in the browser · drawn to match the file
Color
Appearance
Color=Default · Appearance=Solid280 × 360 in the file
three ways a hovered row went invisible

The hover fills for Default / Translucent and Inverted / Solid had been swapped. The light translucent menu hovered to near-black under near-black text, and the dark solid menu hovered to white under near-white text: contrast ratios of 1.06 and 1.02, which is to say the label disappeared under the cursor in both. They are now the right way round, and the light translucent surface itself, which was fully opaque and identical to Solid, drops to 70% like its inverted counterpart.

Destructive / Hover had the same fault a third time: the row filled with base.destructive and the label and icon stayed base.destructive, red on red. Both now take base.destructive-foreground, and the shortcut comes with them rather than staying dark.

The rest of the set

A submenu trigger, a heading and a rule

2 variants · 312 × 28
Context Menu Text
Active

Active fills the row and moves every ink to base.secondary-foreground, the same treatment Hover gives an ordinary item. A submenu that is open should look no different from one you are pointing at.

single · 312 × 28
Label

12 at weight 500 in base.muted-foreground, with the same 8px side padding as a row so the heading and the items it covers share a left edge.

single · 224 × 8

A 1px rule inside an 8px board, so the space above and below comes with it and you do not have to remember to add margin around a hairline.

The usage frame

Ten menus, from three rows to eleven

The second frame is 964 by 1372. The copy is the file's; the radio menus below are live.

Actions on a file, with a destructive option last
Open
Open in new tab⌘↵
Rename
Duplicate⇧⌘D
Share
Copy link
Invite people
Move to trash
A submenu open to the right
Cut
Copy
Paste
Sort by
Radio options - the current view is checked
Inverted menu for a dark canvas
Bring to front
Send to back⌥⌘[
Lock layer
Delete layer
Grouped by labels - a workspace menu and two settings groups
Workspace
Rename
Duplicate
Archive
Members
Invite
Manage roles
Export
Move
Settings
Delete⇧⌘P
Appearance
Density
Add to favourites⌘D
Get info⌘I
Show in folder
Undo
Redo
Paste
In the file

Five components, thirty-four variants

Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.

ContextMenu / ItemVariant × State × Show IconDefault · Shortcut · Radio · Destructive × Default · Hover · Hover (Translucent) · Disabled × No · Yes - Destructive stops at Hover28
ContextMenuColor × AppearanceDefault · Inverted × Solid · Translucent4
ContextMenu / SubTriggerActiveNo · Yes2
ContextMenu / Label · Separatornonea 312 × 28 heading and a 1px rule in an 8px board2

Using the Context Menu

Because Radio reuses the Show Icon axis for its tick instead of for a leading glyph. A radio row is text, then a check, then a fixed 20px spacer, so a chosen and an unchosen option keep the same right edge. It is the one place in the component where an axis name does not describe what it draws.

A destructive row is either offered or it is not there. The file draws it at rest, in red on the menu surface, and hovered, where the whole row fills with base.destructive and the label goes white. Disabling it rather than removing it would leave a red row nobody can press, which reads as a fault.

The hover fill on a row inside a translucent menu, at 70% rather than solid so the surface behind it still shows. It is base.muted carrying the alpha, which is why the row lightens without hiding what the menu is floating over.

It is 312 wide rather than 300 and ends in a chevron. Its Active state gives it exactly what Hover gives an ordinary item, a base.muted fill and base.secondary-foreground ink, so an open submenu and a pointed-at row look the same. That is deliberate: both mean the same thing to the reader.

Overlays · 34 variants