Dropdowns

Dropdowns display a list of actions or options when triggered. They help declutter interfaces by hiding secondary actions until needed.


Installation

Copy the dropdown CSS from styles/docs.css or include the Standard stylesheet:

<link rel="stylesheet" href="standard.min.css">

Then use dropdown classes in your HTML:

<div class="Dropdown">
    <button class="Button Dropdown-trigger">Options <i class="ph ph-caret-down"></i></button>
    <div class="Dropdown-menu">
        <a href="#" class="Dropdown-item">Edit</a>
        <a href="#" class="Dropdown-item">Delete</a>
    </div>
</div>

Usage

Wrap a trigger and menu in .Dropdown. The trigger toggles the .Dropdown-menu visibility.


Examples

Basic Dropdown

A simple dropdown with action items and a divider.

With Icons

Add icons to dropdown items for visual reinforcement.

With Sections

Group related items using headers and dividers.

Checkable Items

Use for multi-select or toggle options within a dropdown.

Icon Button Trigger

Common pattern for contextual action menus.

With Descriptions

Add helper text to clarify actions.

With Keyboard Shortcuts

Display keyboard shortcuts alongside actions.


JavaScript

Basic toggle logic for dropdown interaction:

document.querySelectorAll('.Dropdown').forEach(dropdown => {
    const trigger = dropdown.querySelector('.Dropdown-trigger');
    const menu = dropdown.querySelector('.Dropdown-menu');
    
    trigger.addEventListener('click', (e) => {
        e.stopPropagation();
        // Close other dropdowns
        document.querySelectorAll('.Dropdown-menu.is-open').forEach(m => {
            if (m !== menu) m.classList.remove('is-open');
        });
        menu.classList.toggle('is-open');
    });
});

// Close on outside click
document.addEventListener('click', () => {
    document.querySelectorAll('.Dropdown-menu.is-open')
        .forEach(m => m.classList.remove('is-open'));
});

// Close on Escape key
document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
        document.querySelectorAll('.Dropdown-menu.is-open')
            .forEach(m => m.classList.remove('is-open'));
    }
});

Common Patterns

User Account Menu

Table Row Actions


Customization

Override dropdown styles using CSS custom properties:

/* Custom dropdown menu width */
.Dropdown-menu {
  --dropdown-min-width: 280px;
  min-width: var(--dropdown-min-width);
}

/* Custom item colors */
.Dropdown-item {
  --dropdown-item-hover: oklch(95% 0.02 250);
}

.Dropdown-item:hover {
  background: var(--dropdown-item-hover);
}

/* Custom danger color */
.Dropdown-item--danger {
  --dropdown-danger: oklch(55% 0.2 25);
  color: var(--dropdown-danger);
}

/* Rounded menu */
.Dropdown-menu--rounded {
  border-radius: var(--space-3);
}

API Reference

Container Classes

Class Description
.Dropdown Container for dropdown trigger and menu
.Dropdown-trigger Element that toggles the dropdown (add to button)
.Dropdown-menu The dropdown menu panel
Class Description
.Dropdown-item Individual menu item (link or button)
.Dropdown-item--danger Destructive action styling (red)
.Dropdown-item--check Checkable item with checkbox input
.Dropdown-item--descriptive Item with description text

Structure Classes

Class Description
.Dropdown-header Section header label
.Dropdown-divider Horizontal separator between items
.Dropdown-shortcut Keyboard shortcut display (use with <kbd>)

State Classes

Class Description
.is-open Shows the dropdown menu (add via JS)

CSS Reference

/* Container */
.Dropdown {
  position: relative;
  display: inline-block;
}

/* Menu */
.Dropdown-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 100;
  min-width: 180px;
  padding: var(--space-1) 0;
  background: var(--bg);
  border: 1px solid var(--bd);
  border-radius: var(--r-m);
  box-shadow: 0 4px 12px oklch(0% 0 0 / 0.12);
  display: none;
  margin-top: var(--space-1);
}

.Dropdown-menu.is-open {
  display: block;
}

/* Items */
.Dropdown-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  color: var(--fg);
  text-decoration: none;
  font-size: 0.9rem;
  cursor: pointer;
  transition: background 0.1s;
}

.Dropdown-item:hover {
  background: var(--bg-s);
}

.Dropdown-item--danger {
  color: oklch(55% 0.2 25);
}

.Dropdown-item--danger:hover {
  background: oklch(55% 0.2 25 / 0.08);
}

/* Checkable items */
.Dropdown-item--check {
  cursor: pointer;
}

.Dropdown-item--check input {
  margin: 0;
}

/* Descriptive items */
.Dropdown-item--descriptive {
  padding: var(--space-2) var(--space-3);
}

.Dropdown-item-content {
  display: flex;
  flex-direction: column;
}

.Dropdown-item-label {
  font-weight: 500;
}

.Dropdown-item-description {
  font-size: 0.8rem;
  color: var(--fg-3);
}

/* Divider */
.Dropdown-divider {
  border: none;
  border-top: 1px solid var(--bd);
  margin: var(--space-1) 0;
}

/* Header */
.Dropdown-header {
  padding: var(--space-2) var(--space-3);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--fg-3);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Keyboard shortcut */
.Dropdown-shortcut {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--fg-3);
  font-family: var(--font-sans);
}

/* Trigger caret */
.Dropdown-trigger i.ph-caret-down {
  font-size: 0.8rem;
  transition: transform 0.15s;
}

.Dropdown-trigger[aria-expanded="true"] i.ph-caret-down {
  transform: rotate(180deg);
}

Accessibility

Keyboard Support

Key Action
Enter / Space Opens dropdown, activates focused item
Escape Closes dropdown
Arrow Down Moves focus to next item
Arrow Up Moves focus to previous item
Home Moves focus to first item
End Moves focus to last item
Tab Closes dropdown, moves to next element

ARIA Attributes

<!-- Proper dropdown markup -->
<div class="Dropdown">
    <button 
        class="Button Dropdown-trigger"
        aria-haspopup="true"
        aria-expanded="false"
        aria-controls="dropdown-menu-1"
    >
        Options
        <i class="ph ph-caret-down" aria-hidden="true"></i>
    </button>
    <div 
        class="Dropdown-menu" 
        id="dropdown-menu-1"
        role="menu"
        aria-labelledby="dropdown-trigger-1"
    >
        <a href="#" class="Dropdown-item" role="menuitem">Edit</a>
        <a href="#" class="Dropdown-item" role="menuitem">Duplicate</a>
        <hr class="Dropdown-divider" role="separator">
        <a href="#" class="Dropdown-item Dropdown-item--danger" role="menuitem">Delete</a>
    </div>
</div>

<!-- When open, update aria-expanded -->
<button aria-expanded="true">...</button>

<!-- Checkable items use menuitemcheckbox -->
<label class="Dropdown-item Dropdown-item--check" role="menuitemcheckbox" aria-checked="true">
    <input type="checkbox" checked>
    <span>Show sidebar</span>
</label>

Focus Management

// Move focus into menu when opened
menu.classList.add('is-open');
menu.querySelector('.Dropdown-item')?.focus();

// Return focus to trigger when closed
menu.classList.remove('is-open');
trigger.focus();

Best Practices

Do

  • Group related actions — Use headers and dividers to organize
  • Put destructive actions last — Separate with a divider
  • Use clear action labels — “Delete project” not just “Delete”
  • Add keyboard shortcuts — For frequently used actions
  • Keep menus focused — 5-7 items max before grouping
  • Close on action — Dismiss dropdown after user selects

Don’t

  • Nest dropdowns — Use a different pattern for sub-menus
  • Use for navigation — Dropdowns are for actions, not nav links
  • Hide primary actions — Important actions should be visible
  • Overload with options — Too many items overwhelm users
  • Forget mobile — Ensure touch targets are large enough (44px min)
  • Skip the caret icon — Visual affordance helps discoverability