A raised list of actions or links that opens from a button: grouped items with an icon, a label, a key hint, an optional tick box or single-choice tick, items that open a second menu beside them, and a red one for the destructive action, in two item heights; it can also open at the pointer as a right-click menu.
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
sizesmbk-dropdown--size-<value> | mdsm | md | The height of every item: the medium or the small control height. Figma calls this "Items Size". |
alignsmbk-dropdown--align-<value> | startend | start | Which edge of the opener the panel lines up with. End keeps a menu at the right of a bar inside the window. |
Examples
Usage
A raised panel of actions, each with an icon.
With groups
Group labels, key hints and a divider between groups.
Danger item
The destructive action, last, after a divider.
Small items
The small item height, for dense places.
Ticking items
Each item ticks on and off with a checkbox; the list stays open while several are ticked.
Guidelines
Purpose
A short list that opens from a button and goes away once something is chosen, so a row, a card or a bar can offer several things without showing a button for each. It serves two purposes: a menu of actions on one thing, or a group of links to other pages hanging from a navigation item. An item can open a second menu beside it, for a set of related actions such as the folders a file can move to, and a group of items can be a single choice, such as the order a list is sorted in. The same menu can also open where the pointer is, on a right click over a part of the page such as a card, as a shortcut to that part's actions. Choosing a value from a list is the select component.
When to use
- Three or more actions on one thing: edit, duplicate, move, delete.
- Actions that are rarely used and would clutter a row as buttons.
- A handful of related pages under one navigation item, such as the services of a site under its Services link. The header does this for any link given children.
When not to use
- One or two actions. Show them as buttons.
- Choosing a value. Use a select.
- A site's whole navigation, or links that people need to see at a glance. Show them in the header or the sidebar; a dropdown hides what it holds.
- Opening on hover alone. It must open on a press, so it works with touch and the keyboard.
Usage rules
Do
- Put the destructive action last, after a divider, and mark it danger.
- Keep labels to a verb and an object: "Delete row".
- Use the navigation purpose, with links, for pages; use the actions purpose, with buttons, for commands.
- Align a dropdown at the end of a bar to the end, so it opens inward.
- Put the items of a single choice under a label that names the choice, and mark the current one.
- Use a right-click menu as a shortcut to actions that are also offered where people can see them.
- Keep second menus to one level where you can; each level is one more place to aim the pointer.
Don't
- Hide an item people use often inside a second menu.
- Make a right-click menu the only way to reach an action.
- Give an item with a caret an action of its own: pressing it only opens its menu.
- Mark more than one item danger.
- Mix links and actions in one dropdown. They follow different keyboard rules.
- Mix sizes in one panel. The size belongs to the dropdown, so every item is the same height.
Put the destructive action last, after a divider, and mark it danger, so it is hard to hit by accident.
An unmarked delete in the middle of the list sits one slip away from harmless actions.
Mark only the one destructive action as danger, so the color keeps its warning.
Several danger items dilute the warning until none of them stands out.
Anatomy
- The anchor part, present when the dropdown draws its own opener: it holds the opener and the layer.
- The layer part, around the panel: while the panel shows, the React wrapper lifts it over the whole page so nothing cuts the panel off. Plain markup may leave it out; it has no box of its own until it is lifted.
- The opener, supplied by the consumer: a button or a navigation item. The dropdown tells it whether the panel is open and which panel it controls.
- The area part, present for a right-click menu instead of the anchor: it holds the part of the page the menu opens over, the point part and the layer. The point part is a place with no size, where the pointer was; the panel hangs from it.
- The block, the raised panel. For actions it is a menu; for links it is a named group of links.
- The label part, an optional short heading over a group of items, in small grey words.
- The group part, which holds a label and the items after it, up to the next divider or label, and has the group role, named by the label. The React wrapper adds it; plain markup writes it around the label and its items. It is spaced as the panel is, so it changes nothing on screen.
- The item part, one action or one link, in Figma's order: an optional checkbox part (the checkbox component's small box, ticked or not), an optional icon part, the text part, an optional trailing icon part, and an optional key hint drawn with the kbd component. Icons read in the subtlest text color. A danger item is the destructive action and reads, icons included, in the danger color. In a single choice, the chosen item's trailing icon is a tick.
- The submenu part, which holds an item that opens a second menu and that menu's own layer and panel. Its item ends with a caret pointing to the side the menu opens on, drawn as an icon part.
- The divider part, between groups.
Behavior
With an opener, pressing the opener shows the panel under it, lined up with the opener's start or end edge, and pressing it again, choosing an item, pressing Escape, clicking outside or moving focus away closes it. Only what is open on top closes: a dropdown inside a popover or a modal closes before what holds it. The panel comes out of the opener: it drops across the gap, growing from its edge nearest the opener, and fades in with the enter purpose, and goes back the same way, a little faster, with the exit purpose; when the reader's system asks for less motion it fades in and out in place, without traveling or growing. The opener's caret, where it has one, turns as the panel opens.
The panel is at least the dropdown width and grows to fit its longest item on one line, so a menu of short items keeps one width and a longer item never breaks onto two lines. It grows up to the dropdown's greatest width, and never past the window less a small margin each side; an item longer than that wraps onto more lines inside the panel rather than running past its edge. Both widths are size tokens.
An opener can also be set to open on hover: the panel then opens once a mouse has rested on the opener for a short pause, so a pointer passing over does not open it, and closes a moment after the mouse has left both the opener and the panel, so the pointer can cross from one to the other. Pressing the opener still opens it, because touch and the keyboard have no hover and a menu must never open on hover alone; a click on a panel the pointer opened leaves it open. Only one dropdown opened by hover shows at a time: moving to another opener closes the first at once. The pause and the moment come from the motion tokens' hover purpose.
A right-click menu is the same panel opened over an area of the page rather than from a button. A right click there opens it with its corner at the pointer, and the browser's own menu does not show; another right click elsewhere in the area moves it there. On a touch screen a finger held still on the area for the hold delay opens it where the finger is, and the phone's own callout for a held link or picture does not show there. From the keyboard, Shift+F10 or the menu key opens it under the element that has focus in the area, with focus on its first item, and closing it puts focus back on that element. It moves, closes and is used as any dropdown: the items, the keys, a press outside and Escape are the same.
Without an opener, the panel draws the list and nothing else: opening, closing and placing it belong to whoever shows it. It still fades in when it appears.
A list taller than the list height stops there and scrolls inside the panel, with a soft shade along its foot while more items wait below; the shade fades as the list reaches its end. Its scrollbar is the system's: a thin, see-through bar over its items that takes no room and shows while the mouse is over it or it scrolls. Markup written by hand keeps the browser's own thin bar. Moving through the items with the keys keeps the focused one in view.
An item with a caret opens a second menu at its right, its first item level with it, or at its left when there is no room at the right. It opens when the item is pressed, when the right arrow, Enter or Space is pressed on it, or when a mouse rests on it a moment; the left arrow or Escape closes it again, and it closes a moment after the mouse has left the item and its menu for another item, and whenever the menu it came from closes. Only one second menu of a panel shows at a time. While it shows, its item keeps the hover fill, so the way back is plain. The second menu has the same items, labels and dividers as the first, at the same size, and choosing in it closes every menu. It slides out of its item as it opens, and when the reader's system asks for less motion it only fades. Without script, a second menu given as open hangs at the right of its item, or under it in a narrow space, and the panel around it stops cutting off what overflows it.
A single choice is a group of items of which one is chosen, such as a sort order under a "Sort by" heading: the chosen item draws a tick after its label, and pressing another chooses it and closes the dropdown.
Hovering or focusing an item gives it the hover fill. With the React wrapper, the mouse's hover fill is one shape that slides from item to item with the pointer: between items, on a divider or in the panel's padding it lights the nearest item, and a click there presses that item. It fades in where the pointer enters and out as it leaves, lands without sliding when the reader's system asks for less motion, and steps aside while the keys move, so the focused item keeps its own fill and ring. A touch screen shows no hover fill, and plain HTML keeps each item's own. Items are the medium control height, set in the medium body text with medium icons, or, when the dropdown's size is small, the small control height with the small body text and small icons. Items touch one another, and a long label grows the item past its height. A disabled item dims its words, its icons and its key hint together. An item given a checked value ticks: pressing it flips the tick and leaves the dropdown open, so several can be ticked in one go. In a dropdown of links, a link to the page being shown, marked as the current page, is drawn with the selected surface and its words a weight bolder, as the tabs' more menu marks the current page it holds. The dropdown is the same in narrow and wide spaces; only the alignment is chosen, so a panel at the end of a bar stays inside the window.
Any element can be the opener if it renders one button and passes its id, its aria attributes and its click and key handlers through to that button; the system's button and navigation item both do. The dropdown adds the attributes and runs the opener's own handlers first.
Plain HTML pages get the look and the movement from the stylesheet but none of the behavior, which lives in the React wrapper.
The panel is closed with the hidden attribute. A page that opens and closes the panel itself, with no opener or in plain HTML, adds and removes that attribute, and the panel stays drawn until its exit movement finishes.
With an opener, the React wrapper shows the panel over the whole page, so a part around the opener that clips or scrolls never cuts it off and nothing on the page covers it, while the panel keeps the theme, density and text size of the place it opened from. It places the panel beside the opener on the side with room: under it, or above it when there is more room above, rising into place and growing upward from its foot instead of dropping, and sinking back toward the opener as it closes. The panel is never taller than that room, keeps a small gap from the window's edges, and stays beside the opener as the page scrolls. The placing uses React Aria, which a React project installs beside React. Without script, and in plain HTML, the panel hangs under the opener inside the page, where an ancestor that clips its overflow clips it too.
Accessibility
What it does for you
- The dropdown follows the menu button pattern of the WAI-ARIA Authoring Practices: the opener says whether the panel is open and which panel it controls, and for actions it says it opens a menu.
- For actions, the panel is a menu and each item a menu item. Opening from the keyboard moves focus to the first item; opening with a press moves it to the menu itself, so no item looks chosen, and the arrows start from there; the down and up arrows move between items and wrap around; Home and End jump to the first and last; Escape closes and returns focus to the opener; Tab closes and moves on. The up arrow on the opener opens on the last item. Items are skipped by Tab, because the arrows move between them. Typing a letter moves focus to the next item whose words start with it; letters typed quickly add up.
- For links, the panel is a group named by its label, the items are ordinary links in the tab order, and the opener keeps focus when pressed. The arrows, Home and End move between the links as well, and Escape closes and returns focus to the opener. Navigation is better served by links and lists than by menu roles, which is why this purpose has no menu roles.
- Choosing an action returns focus to the opener, so a keyboard user is never left on a closed panel.
- A ticking item is a menu item checkbox and says whether it is ticked; its drawn box is hidden from assistive technology.
- An item of a single choice is a menu item radio and says whether it is the chosen one; the tick is drawn for the eye only.
- An item that opens a second menu says it opens a menu and whether it is open, and the second menu is named by its item. The right arrow, Enter or Space opens it with focus on its first item; the left arrow or Escape closes it and puts focus back on its item; the arrows, Home, End and typed letters move inside the menu that has focus only. This follows the menu pattern of the WAI-ARIA Authoring Practices.
- A disabled item is reached by the arrows, so a screen reader says it is there and unavailable, and pressing it does nothing.
- A label and the items after it are one group, named by the label, so a screen reader says which group an item is in.
- The key hint is drawn for the eye and hidden from screen readers, which get the shortcut as the item's
aria-keyshortcutsinstead ("⌘ X" becomes Meta+X), so the item's name is its words alone. - The panel's movement never delays focus: focus moves at once and the panel moves around it.
What you must do
- Supply the label, which names the list, and an opener with a visible label or an accessible name.
- For a right-click menu, offer the same actions somewhere people can see them too, such as a menu button on the card: a right-click menu is hidden until someone knows to look for it, and a screen reader does not announce it. Make sure something in the area takes focus, so the keyboard can open it with Shift+F10.
- Wire a key hint's shortcut yourself; the dropdown only shows and announces it.
How to test
- With the keyboard, press Enter on the opener: the panel opens, with focus on the first item for actions. Move with the arrow keys, Home and End, press Enter on an item, and focus returns to the opener. Press Escape to close it without choosing.
- With a screen reader, the opener says whether the panel is open, an actions panel is read as a menu and its items as menu items, and a group is read by its label.
- A ticking item is read as ticked or not ticked, an item of a single choice as checked or not checked, and a disabled item cannot be chosen.
- On an item with a caret, the right arrow opens its menu on the first item, the left arrow and Escape close it with focus back on the item, and a screen reader says the item opens a menu and whether it is open.
- A key hint is not read as part of the item's name; the shortcut is announced separately.
- On a right-click area, a right click opens the menu at the pointer, a finger held still opens it on a touch screen, and Shift+F10 on a focused element in the area opens it with focus on the first item; Escape puts focus back on that element.
- With text at 200% and the page 320 pixels wide, the panel stays inside the window and a long item wraps.
- In a forced-color mode, the focused item and the panel's edge are still visible.
Keyboard
| Key | What it does |
|---|---|
| EnterorSpace | On the opener, opens and closes the panel. In an actions menu, focus moves to the first item; in a links panel, it stays on the opener. A press with the pointer opens an actions menu with focus on the menu, no item. |
| Down Arrow | On the opener, opens the panel on the first item. |
| Up Arrow | On the opener, opens the panel on the last item. |
| Down ArroworUp Arrow | In the panel, moves to the next or previous item, wrapping around, a disabled one included. In a second menu, moves inside it only. |
| Right Arrow | On an item with a caret, opens its menu with focus on its first item. |
| Left Arrow | In a second menu, closes it and returns focus to its item. |
| HomeorEnd | In the panel, moves to the first or last item. |
| A letter | In an actions menu, moves to the next item whose words start with it; letters typed quickly add up. |
| EnterorSpace | On an item, chooses it. A ticking item flips its tick and leaves the panel open. On an item with a caret, opens its menu with focus on its first item. |
| Shift+F10orthe menu key | On an element in a right-click area, opens the menu under it with focus on the first item. |
| Escape | Closes the panel and returns focus to the opener. In a second menu, closes only that menu and returns focus to its item. |
| Tab | In an actions menu, closes the panel and moves on. In a links panel, moves through the links in the tab order. |
Source
The files the dropdown is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-dropdown — Dropdown menu. Figma "Dropdown" 4800:63682 over "DropdownItem" 4210:23371.
* A raised list of actions on the overlay surface: an optional group label,
* items with an optional checkbox, a leading icon, a label, a trailing icon and
* a key hint (or the tick of a single choice, or a submenu's caret), and
* dividers between groups. Items come in two heights, the
* medium and the small control height (Figma's Items Size), each with the text,
* icon, side padding and gap of its height; rows touch, with the extra-small
* inset around the list. Icons read in the subtlest text color, as Figma binds them.
* The panel is at least the dropdown width, carried from Figma's frame, and grows to fit its longest item on
* one line, up to the dropdown's greatest width and the window less its margins; past that a label wraps. A list
* taller than the list height stops there and scrolls, with a shade at its
* foot while there is more below (Figma's Height=Limited and its scroll hint).
* With an opener, the anchor holds the opener and the layer, and the layer the
* panel. Without script the panel hangs under the opener, lined up with its
* start or end edge, the gap Figma draws between the select's well and its list
* below it. With script, the layer is lifted over the page while the panel
* shows and the wrapper places the panel on the side with room (floating.ts).
* A submenu part holds an item that opens a second panel and that panel's
* layer; the second panel is placed at the right of its item the same way.
* A right-click menu is the same panel in an area part, placed from a point
* part where the pointer was. */
.smbk-dropdown {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: var(--smbk-space-0);
width: max-content;
min-width: var(--smbk-size-dropdown-width);
max-width: min(var(--smbk-size-dropdown-max-width), 100vw - 2 * var(--smbk-space-stack-xs));
margin: 0;
padding: var(--smbk-space-inset-xs);
border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-rim);
border-radius: var(--smbk-radius-container-sm);
background: var(--smbk-color-surface-overlay);
color: var(--smbk-color-text-default);
box-shadow: var(--smbk-elevation-overlay);
max-height: var(--smbk-size-list-max-height);
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-width: thin;
scrollbar-color: var(--smbk-color-border-subtle) transparent;
}
/* A menu opened by a press takes focus itself, as a container, not a control: no ring of its own. */
.smbk-dropdown:focus {
outline: none;
}
/* Rows never shrink, so a list that stops at its height scrolls rather than squeezing them. */
.smbk-dropdown > *,
.smbk-dropdown__group > * {
flex-shrink: 0;
}
/* ---- the scroll hint: while the list runs on below its foot, a shade along the foot says there is more. It
fades as the list reaches its end, read from the list's own scrolling; a list that does not scroll has none.
It covers the list's last rows and its foot padding without taking any room. Figma draws black at 8 per
cent; the shade is the small shadow color, the nearest color role. Decoration: it takes no press. ---- */
.smbk-dropdown::after {
content: "";
position: sticky;
bottom: calc(-1 * var(--smbk-space-inset-xs));
flex-shrink: 0;
height: calc(var(--smbk-size-24) + var(--smbk-space-inset-xs));
margin: calc(-1 * var(--smbk-size-24)) calc(-1 * var(--smbk-space-inset-xs)) calc(-1 * var(--smbk-space-inset-xs));
background: linear-gradient(transparent, var(--smbk-color-shadow-sm));
opacity: 0;
pointer-events: none;
}
@supports (animation-timeline: scroll()) {
.smbk-dropdown::after {
animation: smbk-dropdown-more linear both;
animation-timeline: scroll(nearest block);
}
}
@keyframes smbk-dropdown-more {
0%, 90% { opacity: 1; }
100% { opacity: 0; }
}
/* A label and the items after it, up to the next divider, are one group, named by the label for screen
readers. The group is spaced as the panel is, so grouping changes nothing on screen. */
.smbk-dropdown__group {
display: flex;
flex-direction: column;
gap: var(--smbk-space-0);
}
/* The padding makes the row as tall as a large mark, with the line centered in it, and lines its
words up with the items' contents. */
.smbk-dropdown__label {
display: block;
box-sizing: border-box;
padding: calc((var(--smbk-size-mark-lg) - 1lh) / 2) var(--smbk-space-inset-lg);
color: var(--smbk-color-text-subtlest);
font-family: var(--smbk-typography-label-sm-font-family);
font-size: var(--smbk-typography-label-sm-font-size);
font-weight: var(--smbk-typography-label-sm-font-weight);
line-height: var(--smbk-typography-label-sm-line-height);
letter-spacing: var(--smbk-typography-label-sm-letter-spacing);
text-transform: var(--smbk-typography-label-sm-text-transform);
}
/* The medium row: Body MD, a medium icon, the large inset at its sides. The block padding centers one
line in the row's height and keeps the same air around a label that wraps. */
.smbk-dropdown__item {
box-sizing: border-box;
display: flex;
align-items: center;
gap: var(--smbk-space-inline-md);
width: 100%;
min-height: var(--smbk-size-control-md);
margin: 0;
padding: calc((var(--smbk-size-control-md) - 1lh) / 2) var(--smbk-space-inset-lg);
border: 0;
border-radius: var(--smbk-radius-control-sm);
background: transparent;
color: var(--smbk-color-text-default);
font-family: var(--smbk-typography-body-md-font-family);
font-size: var(--smbk-typography-body-md-font-size);
font-weight: var(--smbk-typography-body-md-font-weight);
line-height: var(--smbk-typography-body-md-line-height);
letter-spacing: var(--smbk-typography-body-md-letter-spacing);
text-align: left;
text-decoration: none;
overflow-wrap: break-word;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
-webkit-user-select: none;
user-select: none;
}
/* Under the pointer or the keys, the row takes the hover fill, which shows on the overlay surface in both themes.
With the wrapper the pointer's fill is the glide part instead (below), so a row's own hover is drawn only
in markup without it. */
.smbk-dropdown__item:focus-visible {
background: var(--smbk-color-surface-hover);
}
@media (hover: hover) {
.smbk-dropdown__item:hover:where(:not([data-glide] *)) {
background: var(--smbk-color-surface-hover);
}
}
.smbk-dropdown__item:focus-visible {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: var(--smbk-focus-ring-offset);
}
.smbk-dropdown__item:disabled,
.smbk-dropdown__item[aria-disabled="true"] {
color: var(--smbk-color-text-disabled);
background: transparent;
cursor: not-allowed;
}
.smbk-dropdown__item--danger {
color: var(--smbk-color-action-danger-foreground);
}
.smbk-dropdown__item--danger:disabled,
.smbk-dropdown__item--danger[aria-disabled="true"] {
color: var(--smbk-color-action-danger-foreground-disabled);
}
/* A submenu's item keeps the hover fill while its menu shows, so the way back to it is plain (Figma's Submenu example
draws it in the Hover state). */
.smbk-dropdown__item[aria-expanded="true"] {
background: var(--smbk-color-surface-hover);
}
/* The small row: Body SM, a small icon, the medium inset at its sides and the small gap. */
.smbk-dropdown--size-sm .smbk-dropdown__item {
gap: var(--smbk-space-inline-sm);
min-height: var(--smbk-size-control-sm);
padding: calc((var(--smbk-size-control-sm) - 1lh) / 2) var(--smbk-space-inset-md);
font-family: var(--smbk-typography-body-sm-font-family);
font-size: var(--smbk-typography-body-sm-font-size);
font-weight: var(--smbk-typography-body-sm-font-weight);
line-height: var(--smbk-typography-body-sm-line-height);
letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}
/* A link to the page being shown, in a dropdown of links such as the tabs' More menu: the selected surface and its
words a weight bolder, as the sidebar marks its current page, never by color alone. */
.smbk-dropdown .smbk-dropdown__item[aria-current="page"] {
background: var(--smbk-color-surface-selected);
font-family: var(--smbk-typography-body-md-medium-font-family);
font-weight: var(--smbk-typography-body-md-medium-font-weight);
}
.smbk-dropdown--size-sm .smbk-dropdown__item[aria-current="page"] {
font-family: var(--smbk-typography-body-sm-medium-font-family);
font-weight: var(--smbk-typography-body-sm-medium-font-weight);
}
.smbk-dropdown--size-sm .smbk-dropdown__label {
padding-inline: var(--smbk-space-inset-md);
}
.smbk-dropdown__icon {
flex-shrink: 0;
color: var(--smbk-color-text-subtlest);
font-size: var(--smbk-size-icon-md);
}
.smbk-dropdown--size-sm .smbk-dropdown__icon {
font-size: var(--smbk-size-icon-sm);
}
.smbk-dropdown__item:disabled .smbk-dropdown__icon,
.smbk-dropdown__item[aria-disabled="true"] .smbk-dropdown__icon {
color: var(--smbk-color-text-disabled);
}
/* A disabled item's key hint dims with its words, so the shortcut does not look as if it works. */
.smbk-dropdown__item:disabled .smbk-kbd,
.smbk-dropdown__item[aria-disabled="true"] .smbk-kbd {
color: var(--smbk-color-text-disabled);
}
/* A danger item's icons are its words' color, enabled or disabled. */
.smbk-dropdown__item--danger .smbk-dropdown__icon,
.smbk-dropdown__item--danger:disabled .smbk-dropdown__icon,
.smbk-dropdown__item--danger[aria-disabled="true"] .smbk-dropdown__icon {
color: inherit;
}
/* The checkbox's own box, small, drawn ticked or not (checkbox.css); the item is the control. */
.smbk-dropdown__checkbox {
flex-shrink: 0;
}
.smbk-dropdown__text {
flex: 1 1 auto;
min-width: 0;
}
/* ---- a submenu: its item and the layer of its second panel. The item is drawn as any item, with the caret at its end
(an icon part, in the item's icon color and size, as Figma's Submenu caret). With script, the layer is lifted over
the page while the second panel shows and the wrapper places the panel at the right of the item, its first item level
with it (floating.ts). Without script, an open second panel hangs there from the submenu part, and the panel around
it stops clipping so it shows. ---- */
.smbk-dropdown__submenu {
position: relative;
display: flex;
flex-direction: column;
}
.smbk-dropdown__submenu > .smbk-dropdown__layer:not(:popover-open) > .smbk-dropdown {
top: calc(-1 * (var(--smbk-space-inset-xs) + var(--smbk-border-width-hairline)));
left: calc(100% + var(--smbk-space-inset-xs) + var(--smbk-border-width-hairline) + var(--smbk-space-stack-xs));
}
.smbk-dropdown:has(.smbk-dropdown__submenu > .smbk-dropdown__layer:not(:popover-open) > .smbk-dropdown:not([hidden])) {
overflow: visible;
}
/* In a narrow space there is no room beside the panel: without script, an open second panel hangs under its item,
stepped in from the panel's edge, over the items below it. */
@container style(--smbk-layout: narrow) {
.smbk-dropdown__submenu > .smbk-dropdown__layer:not(:popover-open) > .smbk-dropdown {
top: calc(100% + var(--smbk-space-stack-xs));
left: var(--smbk-space-inset-xl);
}
}
/* The divider keeps the extra-small stack step above and below it, as the rows touch. */
.smbk-dropdown .smbk-dropdown__divider {
margin: var(--smbk-space-stack-xs) 0;
}
/* ---- the glide: with the wrapper, one hover fill per panel slides to the item under the mouse (glide.ts)
instead of each item lighting up on its own, so a pointer in the gap or the padding still lights the
nearest item. The wrapper adds it as the panel's first child and marks the panel data-glide ("lit" while
it shows); the panel's other children are lifted over it. It fades in where it lands and out as the
pointer leaves, and slides with the feedback purpose; under reduced motion it lands at once and only
fades. A touch screen draws none. The select's and combobox's lists and the sidebar's flyout are this
panel, so they glide the same way; their keyboard's highlight keeps its own fill. ---- */
:where(.smbk-dropdown[data-glide]) {
position: relative;
}
:where(.smbk-dropdown[data-glide] > :not(.smbk-dropdown__glide)) {
position: relative;
}
.smbk-dropdown__glide {
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
margin: 0;
border-radius: var(--smbk-radius-control-sm);
background: var(--smbk-color-surface-hover);
opacity: 0;
pointer-events: none;
list-style: none;
transition-property: translate, width, height, opacity;
transition-duration: var(--smbk-motion-feedback-duration);
transition-timing-function: var(--smbk-motion-feedback-easing);
}
.smbk-dropdown[data-glide="lit"] > .smbk-dropdown__glide {
opacity: 1;
}
.smbk-dropdown__glide[data-instant] {
transition-property: opacity;
}
@media (prefers-reduced-motion: reduce) {
.smbk-dropdown__glide {
transition-property: opacity;
}
}
@media (hover: none) {
.smbk-dropdown__glide {
display: none;
}
}
/* ---- the opener: the anchor holds the opener and the layer, the layer the panel hanging under the opener ----
The layer, its lift into the top layer, the closed panel and the panel's motion (it drops in from its opener and
fades, rises when it opened above, and does not travel under reduced motion) are the floating panels' own, shared
with the popover and the tooltip: floating/floating.css. */
.smbk-dropdown__anchor {
position: relative;
display: inline-flex;
}
/* Until the wrapper lifts the layer, the panel hangs from the anchor, as in markup without the layer. */
.smbk-dropdown__anchor > .smbk-dropdown,
.smbk-dropdown__layer > .smbk-dropdown {
position: absolute;
top: calc(100% + var(--smbk-space-stack-xs));
left: 0;
z-index: var(--smbk-layer-raised);
}
.smbk-dropdown__anchor > .smbk-dropdown--align-end,
.smbk-dropdown__layer > .smbk-dropdown--align-end {
right: 0;
left: auto;
}
/* ---- a right-click menu: the area part holds the part of the page the menu opens over, a point the panel hangs from
and the layer. The wrapper puts the point where the pointer was (or at the focused element, from the keyboard) and
places the panel from it (floating.ts). A finger held on the area opens the menu, so the phone's own callout for a
held link or picture is off there. The point is a place, not a shape: no size, no press. ---- */
.smbk-dropdown__area {
position: relative;
-webkit-touch-callout: none;
}
.smbk-dropdown__point {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
pointer-events: none;
}
/* Without script, a right-click menu given as open hangs inside the area, stepped in from its corner, where a pointer
would have been. */
.smbk-dropdown__area > .smbk-dropdown__layer:not(:popover-open) > .smbk-dropdown {
top: var(--smbk-space-inset-2xl);
left: var(--smbk-space-inset-2xl);
}
/* ---- touch: every item takes an invisible press area ----
On a touch screen an invisible area, centred on each item, takes the press out to the comfortable target in each
direction it falls short of it, except where two items touch: there each keeps its own edge, so the items stay one
press each; each side of a divider, an item reaches only as far as the divider's own room. The panel cuts the area off at its edge. What is drawn and every place stay as they are. */
@media (pointer: coarse) {
.smbk-dropdown__item {
position: relative;
}
.smbk-dropdown__item::after {
content: '';
position: absolute;
inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
}
/* A submenu's item sits in its submenu part, which counts as an item beside its neighbours. */
.smbk-dropdown__item + .smbk-dropdown__item::after,
:is(.smbk-dropdown__item, .smbk-dropdown__submenu) + .smbk-dropdown__submenu > .smbk-dropdown__item::after,
.smbk-dropdown__submenu + .smbk-dropdown__item::after {
inset-block-start: 0;
}
.smbk-dropdown__item:has(+ .smbk-dropdown__item)::after,
.smbk-dropdown__item:has(+ .smbk-dropdown__submenu)::after,
.smbk-dropdown__submenu:has(+ :is(.smbk-dropdown__item, .smbk-dropdown__submenu)) > .smbk-dropdown__item::after {
inset-block-end: 0;
}
/* Each side of a divider, halfway into its room. */
.smbk-dropdown__divider + .smbk-dropdown__item::after,
.smbk-dropdown__divider + .smbk-dropdown__submenu > .smbk-dropdown__item::after {
inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-xs) / -1);
}
.smbk-dropdown__item:has(+ .smbk-dropdown__divider)::after,
.smbk-dropdown__submenu:has(+ .smbk-dropdown__divider) > .smbk-dropdown__item::after {
inset-block-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-xs) / -1);
}
}
/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
user's own colors. Nothing here applies outside that mode. */
@media (forced-colors: active) {
.smbk-dropdown__item:disabled,
.smbk-dropdown__item[aria-disabled="true"],
.smbk-dropdown__item:disabled .smbk-kbd,
.smbk-dropdown__item[aria-disabled="true"] .smbk-kbd {
color: GrayText;
}
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| motion.enter.duration--smbk-motion-enter-duration | 320ms | Motion | |
| motion.enter.easing--smbk-motion-enter-easing | cubic-bezier(0.16, 1, 0.3, 1) | Motion | |
| space.stack.xs--smbk-space-stack-xs | 4px | Spacing | |
| motion.exit.duration--smbk-motion-exit-duration | 200ms | Motion | |
| motion.exit.easing--smbk-motion-exit-easing | cubic-bezier(0.7, 0, 0.84, 0) | Motion | |
| space.0--smbk-space-0 | 0px | Spacing | |
| size.dropdown.width--smbk-size-dropdown-width | 240px | Size | |
| size.dropdown.max-width--smbk-size-dropdown-max-width | 320px | Size | |
| space.inset.xs--smbk-space-inset-xs | 4px | Spacing | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.rim--smbk-color-border-rim | light: #ffffffe6dark: #ffffff0dchanges with theme | Color | |
| radius.container.sm--smbk-radius-container-sm | 8px | Radius | |
| color.surface.overlay--smbk-color-surface-overlay | light: #ffffffdark: #2d3237changes with theme | Color | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| elevation.overlay--smbk-elevation-overlay | light: 0px 12px 16px 2px #373d431a, 0px 2px 8px 0px #373d4326dark: 0px 12px 16px 2px #00000026, 0px 2px 8px 0px #00000033 | Elevation | |
| size.list.max-height--smbk-size-list-max-height | 320px | Size | |
| color.border.subtle--smbk-color-border-subtle | light: #c4cdd8dark: #2d3237changes with theme | Color | |
| size.24--smbk-size-24 | 24px | Size | |
| color.shadow.sm--smbk-color-shadow-sm | light: #373d431adark: #00000026changes with theme | Color | |
| size.mark.lg--smbk-size-mark-lg | 24px | Size | |
| space.inset.lg--smbk-space-inset-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| color.text.subtlest--smbk-color-text-subtlest | light: #606a75dark: #7c8692changes with theme | Color | |
| typography.label-sm--smbk-typography-label-sm-font-family--smbk-typography-label-sm-font-size--smbk-typography-label-sm-font-weight--smbk-typography-label-sm-line-height--smbk-typography-label-sm-letter-spacing--smbk-typography-label-sm-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px | The quick brown fox | Typography |
| space.inline.md--smbk-space-inline-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| size.control.md--smbk-size-control-md | comfortable: 40pxcompact: 32pxchanges with density | Size | |
| radius.control.sm--smbk-radius-control-sm | 4px | Radius | |
| typography.body-md--smbk-typography-body-md-font-family--smbk-typography-body-md-font-size--smbk-typography-body-md-font-weight--smbk-typography-body-md-line-height--smbk-typography-body-md-letter-spacing--smbk-typography-body-md-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| color.surface.hover--smbk-color-surface-hover | light: #373d4326dark: #606a754dchanges with theme | Color | |
| focus.ring.width--smbk-focus-ring-width | 2px | Focus | |
| color.border.focus--smbk-color-border-focus | light: #cc0d0ddark: #f34336changes with theme | Color | |
| focus.ring.offset--smbk-focus-ring-offset | 2px | Focus | |
| color.text.disabled--smbk-color-text-disabled | light: #7c8692dark: #606a75changes with theme | Color | |
| color.action.danger.foreground--smbk-color-action-danger-foreground | light: #761812dark: #febdb2changes with theme | Color | |
| color.action.danger.foreground-disabled--smbk-color-action-danger-foreground-disabled | #cc0d0d80 | Color | |
| space.inline.sm--smbk-space-inline-sm | comfortable: 6pxcompact: 4pxchanges with density | Spacing | |
| size.control.sm--smbk-size-control-sm | comfortable: 32pxcompact: 28pxchanges with density | Size | |
| space.inset.md--smbk-space-inset-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| color.surface.selected--smbk-color-surface-selected | light: #373d431adark: #606a7533changes with theme | Color | |
| typography.body-md-medium--smbk-typography-body-md-medium-font-family--smbk-typography-body-md-medium-font-size--smbk-typography-body-md-medium-font-weight--smbk-typography-body-md-medium-line-height--smbk-typography-body-md-medium-letter-spacing--smbk-typography-body-md-medium-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 500lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| typography.body-sm-medium--smbk-typography-body-sm-medium-font-family--smbk-typography-body-sm-medium-font-size--smbk-typography-body-sm-medium-font-weight--smbk-typography-body-sm-medium-line-height--smbk-typography-body-sm-medium-letter-spacing--smbk-typography-body-sm-medium-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| size.icon.md--smbk-size-icon-md | 20px | Size | |
| size.icon.sm--smbk-size-icon-sm | 16px | Size | |
| space.inset.xl--smbk-space-inset-xl | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| motion.feedback.duration--smbk-motion-feedback-duration | 150ms | Motion | |
| motion.feedback.easing--smbk-motion-feedback-easing | cubic-bezier(0.16, 1, 0.3, 1) | Motion | |
| layer.raised--smbk-layer-raised | 1000 | Stacking order | |
| space.inset.2xl--smbk-space-inset-2xl | comfortable: 24pxcompact: 16pxchanges with density | Spacing | |
| size.target.comfortable--smbk-size-target-comfortable | 44px | Size |
Manifest
{
"name": "dropdown",
"block": "smbk-dropdown",
"summary": "A raised list of actions or links that opens from a button: grouped items with an icon, a label, a key hint, an optional tick box or single-choice tick, items that open a second menu beside them, and a red one for the destructive action, in two item heights; it can also open at the pointer as a right-click menu.",
"adapt": "css",
"axes": {
"size": {
"values": [
"md",
"sm"
],
"default": "md",
"description": "The height of every item: the medium or the small control height. Figma calls this \"Items Size\"."
},
"align": {
"values": [
"start",
"end"
],
"default": "start",
"description": "Which edge of the opener the panel lines up with. End keeps a menu at the right of a bar inside the window."
}
},
"flags": {},
"parts": [
"anchor",
"layer",
"group",
"label",
"item",
"checkbox",
"icon",
"text",
"divider",
"glide",
"submenu",
"area",
"point"
],
"figmaNodeId": "4800:63682"
}