Dropdown Multilevel
A two-level dropdown menu where top-level items with children reveal a fly-out submenu panel positioned inline-end, using aria-haspopup=menu + aria-expanded on the parent item, and ArrowRight/ArrowLeft keyboard navigatio…
$ prime install @community/pattern-dropdown-multilevel Projection
Always in _index.xml · the agent never has to ask for this.
DropdownMultilevel [pattern] v1.0.0
A two-level dropdown menu where top-level items with children reveal a fly-out submenu panel positioned inline-end, using aria-haspopup=menu + aria-expanded on the parent item, and ArrowRight/ArrowLeft keyboard navigation between levels per WAI-ARIA Disclosure Navigation pattern.
Loaded when retrieval picks the atom as adjacent / supporting.
DropdownMultilevel [pattern] v1.0.0
A two-level dropdown menu where top-level items with children reveal a fly-out submenu panel positioned inline-end, using aria-haspopup=menu + aria-expanded on the parent item, and ArrowRight/ArrowLeft keyboard navigation between levels per WAI-ARIA Disclosure Navigation pattern.
Label
Multi-Level Dropdown (Fly-out Submenu)
Problem
Standard single-level dropdowns cannot accommodate hierarchical command structures. Fly-out submenus without proper keyboard access exclude users who cannot use a mouse.
Solution
Parent items with children carry aria-haspopup=menu and aria-expanded. ArrowRight opens the submenu and moves focus to its first item; ArrowLeft or Esc closes it and returns focus to the parent item. Submenus position to the inline-end of the parent to avoid overlapping the trigger.
Structure
<div class="dropdown" role="navigation" aria-label="File menu">
<button class="dropdown__trigger" aria-haspopup="menu" aria-expanded="false" type="button">
File
</button>
<ul class="dropdown__menu" role="menu">
<li role="none">
<button class="dropdown__item" role="menuitem" type="button">New</button>
</li>
<li role="none" class="dropdown__item--parent">
<button class="dropdown__item" role="menuitem" aria-haspopup="menu" aria-expanded="false" type="button">
Export
<svg aria-hidden="true" class="chevron-right"><!-- › --></svg>
</button>
<!-- Submenu: positioned to inline-end -->
<ul class="dropdown__submenu" role="menu" aria-label="Export options">
<li role="none"><button class="dropdown__item" role="menuitem" type="button">Export as PDF</button></li>
<li role="none"><button class="dropdown__item" role="menuitem" type="button">Export as CSV</button></li>
<li role="none"><button class="dropdown__item" role="menuitem" type="button">Export as PNG</button></li>
</ul>
</li>
<li role="none">
<button class="dropdown__item" role="menuitem" type="button">Close</button>
</li>
</ul>
</div>
Css
.dropdown { position: relative; display: inline-block; }
.dropdown__menu,
.dropdown__submenu {
position: absolute;
list-style: none;
margin: 0;
padding: 4px;
background: white;
border: 1px solid oklch(85% 0.02 250);
border-radius: 8px;
box-shadow: 0 8px 24px oklch(20% 0.02 250 / 0.12);
min-width: 180px;
display: none;
z-index: 100;
}
.dropdown__menu { top: calc(100% + 4px); left: 0; }
.dropdown__submenu { top: -4px; left: calc(100% + 4px); }
.dropdown[data-open] .dropdown__menu { display: block; }
.dropdown__item--parent[data-submenu-open] .dropdown__submenu { display: block; }
.dropdown__item {
display: flex; align-items: center; justify-content: space-between;
width: 100%; background: transparent; border: none;
padding: 8px 12px; border-radius: 6px; cursor: pointer; font: inherit;
}
.dropdown__item:hover, .dropdown__item:focus-visible {
background: oklch(95% 0.02 250); outline: none;
}
Behavior
- ArrowRight on a parent menuitem opens the submenu and moves focus to its first item.
- ArrowLeft or Esc inside a submenu closes it and returns focus to the parent menuitem.
- Up/Down arrows navigate within the current level; they do not cross level boundaries.
- Esc from the top-level menu closes everything and returns focus to the trigger.
- Limit nesting to 2 levels. A 3rd level is a signal to redesign the IA.
- On hover: open submenu after a 200ms delay to prevent accidental openings during cursor travel.
A11y
- Parent items with submenus carry aria-haspopup=menu and aria-expanded synced to open state.
- Each level is role=menu; each item is role=menuitem (or role=none > menuitem for li wrappers).
- Keyboard: ArrowRight opens, ArrowLeft closes submenu — mirroring WAI-ARIA menu navigation pattern.
- Submenu has aria-label so screen readers announce which submenu opened.
Loaded when retrieval picks the atom as a focal / direct hit.
DropdownMultilevel [pattern] v1.0.0
A two-level dropdown menu where top-level items with children reveal a fly-out submenu panel positioned inline-end, using aria-haspopup=menu + aria-expanded on the parent item, and ArrowRight/ArrowLeft keyboard navigation between levels per WAI-ARIA Disclosure Navigation pattern.
Label
Multi-Level Dropdown (Fly-out Submenu)
Problem
Standard single-level dropdowns cannot accommodate hierarchical command structures. Fly-out submenus without proper keyboard access exclude users who cannot use a mouse.
Solution
Parent items with children carry aria-haspopup=menu and aria-expanded. ArrowRight opens the submenu and moves focus to its first item; ArrowLeft or Esc closes it and returns focus to the parent item. Submenus position to the inline-end of the parent to avoid overlapping the trigger.
Structure
<div class="dropdown" role="navigation" aria-label="File menu">
<button class="dropdown__trigger" aria-haspopup="menu" aria-expanded="false" type="button">
File
</button>
<ul class="dropdown__menu" role="menu">
<li role="none">
<button class="dropdown__item" role="menuitem" type="button">New</button>
</li>
<li role="none" class="dropdown__item--parent">
<button class="dropdown__item" role="menuitem" aria-haspopup="menu" aria-expanded="false" type="button">
Export
<svg aria-hidden="true" class="chevron-right"><!-- › --></svg>
</button>
<!-- Submenu: positioned to inline-end -->
<ul class="dropdown__submenu" role="menu" aria-label="Export options">
<li role="none"><button class="dropdown__item" role="menuitem" type="button">Export as PDF</button></li>
<li role="none"><button class="dropdown__item" role="menuitem" type="button">Export as CSV</button></li>
<li role="none"><button class="dropdown__item" role="menuitem" type="button">Export as PNG</button></li>
</ul>
</li>
<li role="none">
<button class="dropdown__item" role="menuitem" type="button">Close</button>
</li>
</ul>
</div>
Css
.dropdown { position: relative; display: inline-block; }
.dropdown__menu,
.dropdown__submenu {
position: absolute;
list-style: none;
margin: 0;
padding: 4px;
background: white;
border: 1px solid oklch(85% 0.02 250);
border-radius: 8px;
box-shadow: 0 8px 24px oklch(20% 0.02 250 / 0.12);
min-width: 180px;
display: none;
z-index: 100;
}
.dropdown__menu { top: calc(100% + 4px); left: 0; }
.dropdown__submenu { top: -4px; left: calc(100% + 4px); }
.dropdown[data-open] .dropdown__menu { display: block; }
.dropdown__item--parent[data-submenu-open] .dropdown__submenu { display: block; }
.dropdown__item {
display: flex; align-items: center; justify-content: space-between;
width: 100%; background: transparent; border: none;
padding: 8px 12px; border-radius: 6px; cursor: pointer; font: inherit;
}
.dropdown__item:hover, .dropdown__item:focus-visible {
background: oklch(95% 0.02 250); outline: none;
}
Behavior
- ArrowRight on a parent menuitem opens the submenu and moves focus to its first item.
- ArrowLeft or Esc inside a submenu closes it and returns focus to the parent menuitem.
- Up/Down arrows navigate within the current level; they do not cross level boundaries.
- Esc from the top-level menu closes everything and returns focus to the trigger.
- Limit nesting to 2 levels. A 3rd level is a signal to redesign the IA.
- On hover: open submenu after a 200ms delay to prevent accidental openings during cursor travel.
A11y
- Parent items with submenus carry aria-haspopup=menu and aria-expanded synced to open state.
- Each level is role=menu; each item is role=menuitem (or role=none > menuitem for li wrappers).
- Keyboard: ArrowRight opens, ArrowLeft closes submenu — mirroring WAI-ARIA menu navigation pattern.
- Submenu has aria-label so screen readers announce which submenu opened.
Label
Multi-Level Dropdown (Fly-out Submenu)
Problem
Standard single-level dropdowns cannot accommodate hierarchical command structures. Fly-out submenus without proper keyboard access exclude users who cannot use a mouse.
Solution
Parent items with children carry aria-haspopup=menu and aria-expanded. ArrowRight opens the submenu and moves focus to its first item; ArrowLeft or Esc closes it and returns focus to the parent item. Submenus position to the inline-end of the parent to avoid overlapping the trigger.
Structure
<div class="dropdown" role="navigation" aria-label="File menu">
<button class="dropdown__trigger" aria-haspopup="menu" aria-expanded="false" type="button">
File
</button>
<ul class="dropdown__menu" role="menu">
<li role="none">
<button class="dropdown__item" role="menuitem" type="button">New</button>
</li>
<li role="none" class="dropdown__item--parent">
<button class="dropdown__item" role="menuitem" aria-haspopup="menu" aria-expanded="false" type="button">
Export
<svg aria-hidden="true" class="chevron-right"><!-- › --></svg>
</button>
<!-- Submenu: positioned to inline-end -->
<ul class="dropdown__submenu" role="menu" aria-label="Export options">
<li role="none"><button class="dropdown__item" role="menuitem" type="button">Export as PDF</button></li>
<li role="none"><button class="dropdown__item" role="menuitem" type="button">Export as CSV</button></li>
<li role="none"><button class="dropdown__item" role="menuitem" type="button">Export as PNG</button></li>
</ul>
</li>
<li role="none">
<button class="dropdown__item" role="menuitem" type="button">Close</button>
</li>
</ul>
</div>
Css
.dropdown { position: relative; display: inline-block; }
.dropdown__menu,
.dropdown__submenu {
position: absolute;
list-style: none;
margin: 0;
padding: 4px;
background: white;
border: 1px solid oklch(85% 0.02 250);
border-radius: 8px;
box-shadow: 0 8px 24px oklch(20% 0.02 250 / 0.12);
min-width: 180px;
display: none;
z-index: 100;
}
.dropdown__menu { top: calc(100% + 4px); left: 0; }
.dropdown__submenu { top: -4px; left: calc(100% + 4px); }
.dropdown[data-open] .dropdown__menu { display: block; }
.dropdown__item--parent[data-submenu-open] .dropdown__submenu { display: block; }
.dropdown__item {
display: flex; align-items: center; justify-content: space-between;
width: 100%; background: transparent; border: none;
padding: 8px 12px; border-radius: 6px; cursor: pointer; font: inherit;
}
.dropdown__item:hover, .dropdown__item:focus-visible {
background: oklch(95% 0.02 250); outline: none;
}
Behavior
- ArrowRight on a parent menuitem opens the submenu and moves focus to its first item.
- ArrowLeft or Esc inside a submenu closes it and returns focus to the parent menuitem.
- Up/Down arrows navigate within the current level; they do not cross level boundaries.
- Esc from the top-level menu closes everything and returns focus to the trigger.
- Limit nesting to 2 levels. A 3rd level is a signal to redesign the IA.
- On hover: open submenu after a 200ms delay to prevent accidental openings during cursor travel.
A11y
- Parent items with submenus carry aria-haspopup=menu and aria-expanded synced to open state.
- Each level is role=menu; each item is role=menuitem (or role=none > menuitem for li wrappers).
- Keyboard: ArrowRight opens, ArrowLeft closes submenu — mirroring WAI-ARIA menu navigation pattern.
- Submenu has aria-label so screen readers announce which submenu opened.
Source
prime-system/examples/frontend-design/primes/compiled/@community/pattern-dropdown-multilevel/atom.yaml