We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Dropdown
An interactive menu component with keyboard navigation and intelligent positioning. Dropdowns provide flexible menu interfaces with full accessibility support, including arrow key navigation, automatic focus management, and click-outside-to-close behavior. Features smart positioning with Floating UI for automatic flipping and repositioning.
Quick Start
The most basic dropdown requires three components: .dropdown, .dropdown_trigger, and
.dropdown_menu
with .dropdown_item
elements.
<.dropdown id="basic-dropdown">
<.dropdown_trigger as={&button/1}>
Open Dropdown
<svg
class="-mr-1 h-5 w-5 text-whites"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</.dropdown_trigger>
<.dropdown_menu class="w-56 py-1 rounded-md bg-white shadow-xs ring-1 ring-gray-300 focus:outline-none">
<.dropdown_item
:for={option <- ["Account settings", "Billing", "Documentation"]}
class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block px-4 py-2 text-sm"
>
{option}
</.dropdown_item>
</.dropdown_menu>
</.dropdown>
The dropdown component automatically handles opening/closing, keyboard navigation, and focus management. Click the trigger or use Enter/Space to open, then navigate with arrow keys.
Disabled Items
Dropdown items can be disabled using the disabled=true
attribute. Disabled items cannot be focused via keyboard navigation, and do not close the menu when clicked. They remain accessible to screen readers following ARIA accessibility standards.
<.dropdown id="disabled-demo-dropdown">
<.dropdown_trigger as={&button/1}>
Open Dropdown
<svg
class="-mr-1 h-5 w-5 text-whites"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</.dropdown_trigger>
<.dropdown_menu class="w-56 py-1 rounded-md bg-white shadow-xs ring-1 ring-gray-300 focus:outline-none">
<.dropdown_item class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block px-4 py-2 text-sm">
Edit Profile
</.dropdown_item>
<.dropdown_item
disabled={true}
class="text-gray-400 data-disabled:opacity-50 block px-4 py-2 text-sm"
>
Archive (disabled)
</.dropdown_item>
<.dropdown_item class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block px-4 py-2 text-sm">
Settings
</.dropdown_item>
<.dropdown_item class="text-red-700 data-focus:bg-red-100 data-focus:text-red-900 block px-4 py-2 text-sm">
Sign Out
</.dropdown_item>
</.dropdown_menu>
</.dropdown>
Disabled items use aria-disabled="true"
and data-disabled="true"
attributes for accessibility and styling. Use the data-disabled:
variant to style disabled items differently, such as reducing opacity or changing text color.
Styling
Highlighting Active Items
Use the data-focus
data attribute selector to style dropdown items based on their focus state. The
data-focus:
variant applies when an item has focus (via keyboard navigation or hover), responding to keyboard navigation and hover states.
<.dropdown id="styled-dropdown">
<.dropdown_trigger as={&button/1}>
Options
<svg
class="-mr-1 h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</.dropdown_trigger>
<.dropdown_menu
class="w-56 divide-y divide-gray-100 rounded-md bg-white shadow-xs ring-1 ring-gray-300 focus:outline-none"
transition_enter={
{"ease-out duration-100", "transform opacity-0 scale-95", "transform opacity-100 scale-100"}
}
transition_leave={
{"transition ease-in duration-75", "transform opacity-100 scale-100",
"transform opacity-0 scale-95"}
}
>
<div class="py-1">
<.dropdown_item class="text-gray-700 data-focus:bg-indigo-100 data-focus:text-indigo-900 block px-4 py-2 text-sm transition-colors duration-150">
<svg
class="mr-3 h-4 w-4 text-gray-400 data-focus:text-indigo-500 inline"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
/>
</svg>
Edit Profile
</.dropdown_item>
<.dropdown_item class="text-gray-700 data-focus:bg-indigo-100 data-focus:text-indigo-900 block px-4 py-2 text-sm transition-colors duration-150">
<svg
class="mr-3 h-4 w-4 text-gray-400 data-focus:text-indigo-500 inline"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
Settings
</.dropdown_item>
<.dropdown_item class="text-gray-700 data-focus:bg-indigo-100 data-focus:text-indigo-900 block px-4 py-2 text-sm transition-colors duration-150">
<svg
class="mr-3 h-4 w-4 text-gray-400 data-focus:text-indigo-500 inline"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
/>
</svg>
Analytics
</.dropdown_item>
</div>
<div class="py-1">
<.dropdown_item class="text-gray-700 data-focus:bg-red-100 data-focus:text-red-900 block px-4 py-2 text-sm transition-colors duration-150">
<svg
class="mr-3 h-4 w-4 text-gray-400 data-focus:text-red-500 inline"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
Sign Out
</.dropdown_item>
</div>
</.dropdown_menu>
</.dropdown>
Items without focus use their default styling, while items with the data-focus
attribute get the enhanced styling. This creates a clear visual indication of the currently focused item without requiring separate styles for unfocused states.
You can customize the focused state styling by modifying the data-focus:
classes. Common patterns include background color changes, text color variations, and subtle animations to enhance the user experience.
Smart Positioning
Powered by Floating UI, the dropdown intelligently positions itself using the placement
and offset
attributes.
It automatically flips to the opposite side when there's insufficient space and repositions on scroll or resize.
<div class="flex space-x-8 justify-center">
<!-- Top-start placement -->
<.dropdown id="top-dropdown">
<.dropdown_trigger class="inline-flex justify-center rounded-lg bg-zinc-900 gap-x-1.5 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-700 active:text-white/80">
Top Menu
<svg
class="-mr-1 h-5 w-5 rotate-180"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</.dropdown_trigger>
<.dropdown_menu
placement="top-start"
class="w-48 py-1rounded-md bg-white shadow-xs ring-1 ring-gray-300 focus:outline-none"
transition_enter={
{"ease-out duration-100", "transform opacity-0 scale-95",
"transform opacity-100 scale-100"}
}
transition_leave={
{"transition ease-in duration-75", "transform opacity-100 scale-100",
"transform opacity-0 scale-95"}
}
>
<.dropdown_item class="text-gray-700 data-focus:bg-blue-100 data-focus:text-blue-900 block px-4 py-2 text-sm">
Option A
</.dropdown_item>
<.dropdown_item class="text-gray-700 data-focus:bg-blue-100 data-focus:text-blue-900 block px-4 py-2 text-sm">
Option B
</.dropdown_item>
</.dropdown_menu>
</.dropdown>
<!-- Right-start placement -->
<.dropdown id="right-dropdown">
<.dropdown_trigger class="inline-flex justify-center rounded-lg bg-zinc-900 gap-x-1.5 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-700 active:text-white/80">
Right Menu
<svg class="-mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</.dropdown_trigger>
<.dropdown_menu
placement="right-start"
class="w-48 py-1rounded-md bg-white shadow-xs ring-1 ring-gray-300 focus:outline-none"
transition_enter={
{"ease-out duration-100", "transform opacity-0 scale-95",
"transform opacity-100 scale-100"}
}
transition_leave={
{"transition ease-in duration-75", "transform opacity-100 scale-100",
"transform opacity-0 scale-95"}
}
>
<.dropdown_item class="text-gray-700 data-focus:bg-green-100 data-focus:text-green-900 block px-4 py-2 text-sm">
Option X
</.dropdown_item>
<.dropdown_item class="text-gray-700 data-focus:bg-green-100 data-focus:text-green-900 block px-4 py-2 text-sm">
Option Y
</.dropdown_item>
</.dropdown_menu>
</.dropdown>
<!-- Bottom-end placement with larger offset -->
<.dropdown id="bottom-dropdown">
<.dropdown_trigger class="inline-flex justify-center rounded-lg bg-zinc-900 gap-x-1.5 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-700 active:text-white/80">
Bottom Menu
<svg class="-mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</.dropdown_trigger>
<.dropdown_menu
placement="bottom-end"
class="w-48 py-1 rounded-md bg-white shadow-xs ring-1 ring-gray-300 focus:outline-none"
transition_enter={
{"ease-out duration-100", "transform opacity-0 scale-95",
"transform opacity-100 scale-100"}
}
transition_leave={
{"transition ease-in duration-75", "transform opacity-100 scale-100",
"transform opacity-0 scale-95"}
}
>
<.dropdown_item class="text-gray-700 data-focus:bg-purple-100 data-focus:text-purple-900 block px-4 py-2 text-sm">
Option 1
</.dropdown_item>
<.dropdown_item class="text-gray-700 data-focus:bg-purple-100 data-focus:text-purple-900 block px-4 py-2 text-sm">
Option 2
</.dropdown_item>
</.dropdown_menu>
</.dropdown>
</div>
Use placement="top-start", placement="right-start", placement="bottom-end", or other positioning options.
The offset attribute controls the distance in pixels from the trigger button.
The component automatically repositions when scrolling or resizing to stay visible and accessible.
Keyboard Interaction
The dropdown component provides full keyboard accessibility with comprehensive navigation support. All interactions follow standard ARIA patterns for menu components. Try typing letters like "a", "b", or "d" when the menu is open to see typeahead search in action.
| Key | Description |
|---|---|
| When the menu button is focused: | |
| Enter / Space | Opens the menu and focuses the first non-disabled item |
| ↓ | Opens the menu and focuses the first non-disabled item |
| ↑ | Opens the menu and focuses the last non-disabled item |
| When the menu is open: | |
| Esc | Closes the menu and returns focus to the trigger button |
| ↑ / ↓ | Focuses the previous/next non-disabled item (wraps around) |
| Home / PageUp | Focuses the first non-disabled item |
| End / PageDown | Focuses the last non-disabled item |
| Enter / Space | Activates/clicks the currently focused menu item |
| A-Z / 0-9 | Focuses the first item that starts with the typed character. Repeated presses cycle through matching items. |