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 id="basic-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
id="basic-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, index} <- Enum.with_index(["Account settings", "Billing", "Documentation"])}
id={"basic-dropdown-item-#{index}"}
class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block w-full px-4 py-2 text-sm text-left"
>
{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 id="disabled-demo-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
id="disabled-demo-menu"
class="w-56 py-1 rounded-md bg-white shadow-xs ring-1 ring-gray-300 focus:outline-none"
>
<.dropdown_item
id="disabled-demo-item-0"
class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block w-full px-4 py-2 text-sm text-left"
>
Edit Profile
</.dropdown_item>
<.dropdown_item
id="disabled-demo-item-1"
disabled={true}
class="text-gray-400 data-disabled:opacity-50 block w-full px-4 py-2 text-sm text-left"
>
Archive (disabled)
</.dropdown_item>
<.dropdown_item
id="disabled-demo-item-2"
class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block w-full px-4 py-2 text-sm text-left"
>
Settings
</.dropdown_item>
<.dropdown_item
id="disabled-demo-item-3"
class="text-red-700 data-focus:bg-red-100 data-focus:text-red-900 block w-full px-4 py-2 text-sm text-left"
>
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.
Sections
Organize complex dropdown menus using .dropdown_section, .dropdown_heading, and
.dropdown_separator
components.
<.dropdown id="sections-dropdown">
<.dropdown_trigger id="sections-dropdown-trigger" as={&button/1}>
Account Menu
<svg
class="-mr-1 h-5 w-5 text-white"
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
id="sections-dropdown-menu"
class="w-56 py-1 rounded-md bg-white shadow-xs ring-1 ring-gray-300 focus:outline-none"
>
<.dropdown_section class="px-1 py-1">
<.dropdown_heading
id="sections-dropdown-heading-account"
class="px-3 py-2 text-xs font-semibold text-gray-500 uppercase"
>
Account
</.dropdown_heading>
<.dropdown_item
id="sections-dropdown-item-0"
class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block w-full px-4 py-2 text-sm text-left rounded"
>
Profile
</.dropdown_item>
<.dropdown_item
id="sections-dropdown-item-1"
class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block w-full px-4 py-2 text-sm text-left rounded"
>
Settings
</.dropdown_item>
</.dropdown_section>
<.dropdown_separator class="my-1 border-t border-gray-200" />
<.dropdown_section class="px-1 py-1">
<.dropdown_heading
id="sections-dropdown-heading-support"
class="px-3 py-2 text-xs font-semibold text-gray-500 uppercase"
>
Support
</.dropdown_heading>
<.dropdown_item
id="sections-dropdown-item-2"
class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block w-full px-4 py-2 text-sm text-left rounded"
>
Documentation
</.dropdown_item>
<.dropdown_item
id="sections-dropdown-item-3"
class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block w-full px-4 py-2 text-sm text-left rounded"
>
Contact Us
</.dropdown_item>
</.dropdown_section>
<.dropdown_separator class="my-1 border-t border-gray-200" />
<.dropdown_item
id="sections-dropdown-item-4"
class="text-red-700 data-focus:bg-red-100 data-focus:text-red-900 block w-full px-4 py-2 text-sm text-left rounded"
>
Sign Out
</.dropdown_item>
</.dropdown_menu>
</.dropdown>
Sections provide semantic grouping with proper ARIA labels, headings label each section, and separators create visual divisions between groups.
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 id="styled-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
id="styled-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
id="styled-dropdown-item-0"
class="text-gray-700 data-focus:bg-indigo-100 data-focus:text-indigo-900 block w-full px-4 py-2 text-sm text-left 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
id="styled-dropdown-item-1"
class="text-gray-700 data-focus:bg-indigo-100 data-focus:text-indigo-900 block w-full px-4 py-2 text-sm text-left 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
id="styled-dropdown-item-2"
class="text-gray-700 data-focus:bg-indigo-100 data-focus:text-indigo-900 block w-full px-4 py-2 text-sm text-left 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
id="styled-dropdown-item-3"
class="text-gray-700 data-focus:bg-red-100 data-focus:text-red-900 block w-full px-4 py-2 text-sm text-left 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
id="top-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
id="top-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
id="top-dropdown-item-0"
class="text-gray-700 data-focus:bg-blue-100 data-focus:text-blue-900 block w-full px-4 py-2 text-sm text-left"
>
Option A
</.dropdown_item>
<.dropdown_item
id="top-dropdown-item-1"
class="text-gray-700 data-focus:bg-blue-100 data-focus:text-blue-900 block w-full px-4 py-2 text-sm text-left"
>
Option B
</.dropdown_item>
</.dropdown_menu>
</.dropdown>
<!-- Right-start placement -->
<.dropdown id="right-dropdown">
<.dropdown_trigger
id="right-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
id="right-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
id="right-dropdown-item-0"
class="text-gray-700 data-focus:bg-green-100 data-focus:text-green-900 block w-full px-4 py-2 text-sm text-left"
>
Option X
</.dropdown_item>
<.dropdown_item
id="right-dropdown-item-1"
class="text-gray-700 data-focus:bg-green-100 data-focus:text-green-900 block w-full px-4 py-2 text-sm text-left"
>
Option Y
</.dropdown_item>
</.dropdown_menu>
</.dropdown>
<!-- Bottom-end placement with larger offset -->
<.dropdown id="bottom-dropdown">
<.dropdown_trigger
id="bottom-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
id="bottom-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
id="bottom-dropdown-item-0"
class="text-gray-700 data-focus:bg-purple-100 data-focus:text-purple-900 block w-full px-4 py-2 text-sm text-left"
>
Option 1
</.dropdown_item>
<.dropdown_item
id="bottom-dropdown-item-1"
class="text-gray-700 data-focus:bg-purple-100 data-focus:text-purple-900 block w-full px-4 py-2 text-sm text-left"
>
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.
Match Trigger Width
Use match_trigger_width=true
on .dropdown_menu
to make the menu at least as wide as its trigger. This is useful for select-style dropdowns where the menu should align with the trigger's edges.
<.dropdown id="match-width-dropdown">
<.dropdown_trigger
id="match-width-dropdown-trigger"
class="w-64 inline-flex justify-between items-center rounded-lg bg-white border border-gray-300 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50"
>
Select an option
<svg class="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
id="match-width-dropdown-menu"
placement="bottom-start"
match_trigger_width={true}
class="py-1 rounded-md bg-white shadow-xs ring-1 ring-gray-300 focus:outline-none"
>
<.dropdown_item
:for={{option, index} <- Enum.with_index(["Account settings", "Billing", "Documentation"])}
id={"match-width-dropdown-item-#{index}"}
class="text-gray-700 data-focus:bg-gray-100 data-focus:text-gray-900 block w-full px-4 py-2 text-sm text-left"
>
{option}
</.dropdown_item>
</.dropdown_menu>
</.dropdown>
The menu's min-width
is set to the trigger's width, so the menu can still grow wider if its content requires it. The width is recalculated automatically on scroll and resize.
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. |