Menu (Dropdown Menu and Context Menu)
قائمة الأوامرrole="menu" + role="menuitem"
also calleddropdown menu، context menu، right-click menu، action menu، overflow menu، kebab menu، منيو، قائمة السياق، قائمة كليك يمين، قائمة الثلاث نقاط، قائمة الأوامر المنسدلة، دروب داون منيو
A menu is a small panel that opens over the interface and lists commands ready to run; pressing a row fires the command and the panel closes. It comes in two shapes built from the same parts: a dropdown menu opened by a visible button such as a three dot control, and a context menu opened by the right mouse button or the ContextMenu key on whatever the user is standing on. No row in it stores a value, which is why menuitem does not support aria-selected at all. It is not a select or a listbox: a select holds a value that stays on the trigger and submits with the form, a listbox marks its choice with aria-selected and keeps it after closing, and the split is what happens on press, running a command against setting a value something reads later. It is not a popover either, because a popover is a floating box that takes any content with no semantics of its own, while role="menu" is a strict contract: menuitem-shaped children, arrow-key navigation, and one Tab stop for the whole thing. And it is not a list of links: site navigation is a <ul> of <a> elements, and role="menu" on that list with role="menuitem" on the anchors strips the link role, so they drop out of the screen reader links list and the page promises arrow-key navigation that nobody wrote. Keep role="menu" for application commands such as Edit and Sort by, and leave links as links inside <nav>.
If you called it…
Live specimen
Interact with the demo. Every part is real and numbered.
اضغط بالزرّ الأيمن في أي مكان هنا لفتح قائمة السياق.
آخر أمر: فتح · الشبكة: ظاهرة
Anatomy: every part, named
Hover a row to locate it in the demo above.
Build prompt
How it behaves right-to-left
This section is ours alone.
Which side the submenu opens on
mirrorsA submenu follows the reading direction: it opens to the right of its parent row in English and to the left in Arabic, which is the logical end of that row. Compute the side when the menu opens instead of writing it once in CSS, because it has to flip to the opposite side as soon as the remaining viewport width runs out. Floating UI is logical in its alignment only: the -start and -end suffixes read direction off the element, while the main side in placement is spelled physically (top, right, bottom, left), so either you pick "left-start" yourself under rtl, or you hand the job to a component that reads direction, such as Radix, where dir="rtl" belongs on DropdownMenu.Root or on a DirectionProvider around the tree and never on DropdownMenu.Sub, which takes no dir prop.
The submenu arrow
mirrorsThe arrow at the end of the row points at the side the submenu will open on, so it is purely directional and has to flip with the page. If you draw it as a text character, do not assume the bidi engine will handle it: Bidi_Mirrored is Yes for › (U+203A), which mirrors on its own, and No for → (U+2192) and ▸ (U+25B8), which stay exactly as drawn whatever the direction is. Put the flip in a dedicated class such as .flip-x { transform: scaleX(-1) } under [dir="rtl"] and apply it to this arrow alone, because a blanket rule on svg mirrors the checkmark and the copy and delete glyphs along with it.
The mark column and the shortcut column
mirrorsEvery row is three slots: the state mark at the logical start, the label in the middle, and the shortcut hint at the logical end, so the two outer slots trade places with page direction. Reserve the mark slot on every row with padding-inline-start or an empty element of the same width, and push the shortcut out with margin-inline-start: auto. The ready-made trap here is the DropdownMenuShortcut component in shadcn/ui: its class is ml-auto, which works in English and leaves the shortcut glued to the wrong side in Arabic until you swap it for ms-auto.
The shortcut text itself
never mirrorsThe slot moves with the page, but the order inside the run never changes: Ctrl+K reads the same in both directions. The risk sits in the symbols that lead it, because ⌘ (U+2318) and ⇧ (U+21E7) carry bidi class ON, meaning neutral, and a neutral standing at the edge of a run takes the direction of the paragraph around it, hops to the other side of the letter, and the eye reads K⌘ instead of ⌘K. Isolate the run as one block with dir="ltr" plus unicode-bidi: isolate, or wrap it in <bdi>, rather than patching it with an extra space or a control character typed into the string by hand.
ArrowRight and ArrowLeft
mirrorsThe two keys trade jobs with direction: in Arabic ArrowLeft opens the submenu and ArrowRight closes it, and in English it is the other way round. Read the direction once from getComputedStyle(el).direction and derive openKey and closeKey from it, instead of scattering an rtl check through every branch of the key handler. Nothing else moves: ArrowDown, ArrowUp, Home, and End work on DOM order, Escape closes and restores focus, and first-letter type-ahead has no relationship to direction at all.
In code
Each row is one framework's word for the same thing. Take the row your project speaks.
See also
Updated · 2026-08-19