Dropdown
Dropdowns display a list of actions or options in a compact menu, triggered by a button or interactive element.
Installation
The dropdown component requires Alpine.js. Include Alpine in your layout:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
Usage
Basic Dropdown
<x-ui.dropdown>
<x-slot:trigger>
<x-ui.button>
Open Menu
<x-icon.chevron-down class="w-4 h-4 ml-2" />
</x-ui.button>
</x-slot:trigger>
<x-slot:content>
<a href="#" class="block px-4 py-2 hover:bg-accent">Profile</a>
<a href="#" class="block px-4 py-2 hover:bg-accent">Settings</a>
<a href="#" class="block px-4 py-2 hover:bg-accent">Logout</a>
</x-slot:content>
</x-ui.dropdown>
Dropdown with Icon Trigger
<x-ui.dropdown align="end">
<x-slot:trigger>
<button class="p-2 hover:bg-accent rounded-md">
<x-icon.more-vertical class="w-5 h-5" />
</button>
</x-slot:trigger>
<x-slot:content>
<button class="w-full text-left px-4 py-2 hover:bg-accent flex items-center gap-2">
<x-icon.copy class="w-4 h-4" />
Copy
</button>
<button class="w-full text-left px-4 py-2 hover:bg-accent flex items-center gap-2">
<x-icon.edit class="w-4 h-4" />
Edit
</button>
<button class="w-full text-left px-4 py-2 hover:bg-accent flex items-center gap-2 text-destructive">
<x-icon.trash class="w-4 h-4" />
Delete
</button>
</x-slot:content>
</x-ui.dropdown>
Split Button
<div class="flex -space-x-px">
<x-ui.button rounded-r="none">Save</x-ui.button>
<x-ui.dropdown>
<x-slot:trigger>
<x-ui.button rounded-l="none" pill>
<x-icon.chevron-down class="w-4 h-4" />
</x-ui.button>
</x-slot:trigger>
<x-slot:content>
<button class="w-full text-left px-4 py-2 hover:bg-accent">Save as Draft</button>
<button class="w-full text-left px-4 py-2 hover:bg-accent">Save & Publish</button>
<button class="w-full text-left px-4 py-2 hover:bg-accent">Save & Close</button>
</x-slot:content>
</x-ui.dropdown>
</div>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
align |
string | 'start' |
Horizontal alignment: start, center, end |
side |
string | 'bottom' |
Vertical side: top, bottom |
offset |
number | 4 |
Distance from trigger in pixels |
Examples
User Menu
<x-ui.dropdown align="end">
<x-slot:trigger>
<button class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center">
<span class="text-sm font-medium">JD</span>
</div>
<x-icon.chevron-down class="w-4 h-4" />
</button>
</x-slot:trigger>
<x-slot:content>
<div class="px-4 py-2 border-b">
<p class="font-medium">John Doe</p>
<p class="text-sm text-muted-foreground">john@example.com</p>
</div>
<a href="/profile" class="block px-4 py-2 hover:bg-accent flex items-center gap-2">
<x-icon.user class="w-4 h-4" />
Profile
</a>
<a href="/settings" class="block px-4 py-2 hover:bg-accent flex items-center gap-2">
<x-icon.settings class="w-4 h-4" />
Settings
</a>
<a href="/billing" class="block px-4 py-2 hover:bg-accent flex items-center gap-2">
<x-icon.credit-card class="w-4 h-4" />
Billing
</a>
<div class="border-t my-1"></div>
<form method="POST" action="/logout">
@csrf
<button type="submit" class="w-full text-left px-4 py-2 hover:bg-accent flex items-center gap-2 text-destructive">
<x-icon.log-out class="w-4 h-4" />
Logout
</button>
</form>
</x-slot:content>
</x-ui.dropdown>
Table Actions
<table>
<tbody>
@foreach($users as $user)
<tr>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>
<x-ui.dropdown align="end">
<x-slot:trigger>
<button class="p-2 hover:bg-accent rounded">
<x-icon.more-horizontal class="w-4 h-4" />
</button>
</x-slot:trigger>
<x-slot:content>
<a href="/users/{{ $user->id }}/edit" class="block px-4 py-2 hover:bg-accent">
Edit
</a>
<button wire:click="duplicate({{ $user->id }})" class="w-full text-left px-4 py-2 hover:bg-accent">
Duplicate
</button>
<div class="border-t my-1"></div>
<button wire:click="delete({{ $user->id }})" class="w-full text-left px-4 py-2 hover:bg-accent text-destructive">
Delete
</button>
</x-slot:content>
</x-ui.dropdown>
</td>
</tr>
@endforeach
</tbody>
</table>
Share Menu
<x-ui.dropdown>
<x-slot:trigger>
<x-ui.button variant="outline" size="sm">
<x-icon.share-2 class="w-4 h-4 mr-2" />
Share
</x-ui.button>
</x-slot:trigger>
<x-slot:content>
<button wire:click="share('twitter')" class="w-full text-left px-4 py-2 hover:bg-accent flex items-center gap-2">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<!-- Twitter icon -->
</svg>
Twitter
</button>
<button wire:click="share('facebook')" class="w-full text-left px-4 py-2 hover:bg-accent flex items-center gap-2">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<!-- Facebook icon -->
</svg>
Facebook
</button>
<button wire:click="share('linkedin')" class="w-full text-left px-4 py-2 hover:bg-accent flex items-center gap-2">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<!-- LinkedIn icon -->
</svg>
LinkedIn
</button>
<div class="border-t my-1"></div>
<button wire:click="copyLink" class="w-full text-left px-4 py-2 hover:bg-accent flex items-center gap-2">
<x-icon.link class="w-4 h-4" />
Copy Link
</button>
</x-slot:content>
</x-ui.dropdown>
Filter Dropdown
<x-ui.dropdown>
<xslot:trigger>
<x-ui.button variant="outline">
<x-icon.filter class="w-4 h-4 mr-2" />
Filter
</x-ui.button>
</x-slot:trigger>
<x-slot:content>
<div class="p-4 w-64">
<h4 class="font-medium mb-4">Filters</h4>
<div class="space-y-4">
<div>
<label class="text-sm font-medium">Status</label>
<div class="mt-2 space-y-1">
<label class="flex items-center gap-2">
<input type="checkbox" wire:model="filters.active" />
<span class="text-sm">Active</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" wire:model="filters.inactive" />
<span class="text-sm">Inactive</span>
</label>
</div>
</div>
<div>
<label class="text-sm font-medium">Date Range</label>
<x-ui.input type="date" class="mt-1" />
</div>
</div>
<div class="flex gap-2 mt-4">
<x-ui.button size="sm" variant="outline" wire:click="resetFilters">
Reset
</x-ui.button>
<x-ui.button size="sm" wire:click="applyFilters">
Apply
</x-ui.button>
</div>
</div>
</x-slot:content>
</x-ui.dropdown>
Alpine.js Integration
The dropdown uses Alpine.js for state management:
<div x-data="{ open: false }">
<!-- Trigger -->
<button @click="open = !open" @click.away="open = false">
Toggle Dropdown
</button>
<!-- Content -->
<div
x-show="open"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
>
Dropdown content
</div>
</div>
Accessibility
Dropdowns include comprehensive accessibility features:
ARIA Attributes
aria-haspopup="true"on triggeraria-expandedindicates staterole="menu"on contentrole="menuitem"on items- Proper focus management
Keyboard Navigation
Enter/Space- Open menuEscape- Close menuArrow Down- Next itemArrow Up- Previous itemHome- First itemEnd- Last item
Styling
Customize dropdown styles:
.dropdown-content {
@apply z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1;
@apply text-popover-foreground shadow-md;
}
.dropdown-item {
@apply relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5;
@apply text-sm outline-none transition-colors;
}
.dropdown-item:hover {
@apply bg-accent text-accent-foreground;
}
.dropdown-item:focus {
@apply bg-accent text-accent-foreground;
}
Best Practices
- Limit to 5-7 items for optimal UX
- Group related items with dividers
- Use destructive actions sparingly and place at bottom
- Provide clear labels for actions
- Consider alternatives for many options (dialog, full menu)
- Ensure keyboard accessibility