Simple Flyouts
Simple Flyout Menu
A basic flyout menu with icon and text items. Perfect for navigation menus and simple dropdowns.
<!--
// Set to true to show the flyout open by default. Change to false to have it closed by default.
// open: true
-->
<div
class="relative h-80"
x-data="{
uid: Math.random().toString(36).slice(2),
style: '',
gap: 8,
align() {
const btn = this.$refs.btn;
if (!btn) return;
const r = btn.getBoundingClientRect();
// Center under button, clamp within viewport a bit
const cx = r.left + r.width / 2;
const top = r.bottom + this.gap;
this.style = `position:fixed; left:${cx}px; top:${top}px; transform:translateX(-50%);`;
},
toggle() {
if (this.open) { this.open = false; return; }
window.dispatchEvent(new CustomEvent('close-all-flyouts', { detail: this.uid }));
this.align(); // compute coords first
requestAnimationFrame(() => this.open = true); // then show (no jitter)
},
close() { this.open = false; }
}"
x-init="
// Close when another flyout opens
window.addEventListener('close-all-flyouts', (e) => { if (e.detail !== uid) open = false });
// Keep position synced while open
const realign = () => { if (open) align() };
window.addEventListener('resize', realign);
window.addEventListener('scroll', realign, { passive: true });
// If open by default, align immediately
if (open) align();
"
>
<button
class="relative flex items-center justify-center text-center font-medium transition-colors duration-200 ease-in-out select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:z-10 rounded-md text-zinc-600 bg-zinc-50 outline outline-zinc-100 hover:bg-zinc-200 focus-visible:outline-zinc-600 dark:text-zinc-100 dark:bg-zinc-800 dark:outline-zinc-800 dark:hover:bg-zinc-700 dark:focus-visible:outline-zinc-700 h-9 px-4 text-sm gap-2.5"
x-ref="btn"
@click.stop="toggle"
aria-expanded="false"
>
<span>Button</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler-chevron-down size-4"
slot="right-icon"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 9l6 6l6 -6"></path>
</svg>
</button>
<!-- Backdrop -->
<div
x-show="open"
x-transition.opacity
class="fixed inset-0 z-(--z-overlay-backdrop)"
@click="close"
style="display: none"
></div>
<!-- Teleported dropdown (no clipping) -->
<div
x-show="open"
:style="style"
class="z-(--z-overlay-panel)"
style="display: none"
@keydown.escape.window="close"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 translate-y-1"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 translate-y-1"
>
<div class="max-w-max">
<div
class="w-screen max-w-xs p-4 bg-white shadow-lg dark:bg-zinc-800 rounded-xl outline outline-zinc-900/5 dark:outline-white/10"
>
<div>
<a
class="text-sm relative flex items-center w-full p-4 rounded-lg gap-2 group hover:bg-zinc-50 dark:hover:bg-white/5"
href="#_"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler-arrow-refresh size-5 group-hover:text-zinc-500 dark:group-hover:text-zinc-400 text-zinc-500 dark:text-zinc-400"
slot="left-icon"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
</svg>
<div
class="text-base font-medium text-zinc-900 dark:text-white group-hover:text-zinc-500 dark:group-hover:text-zinc-400"
>
Sync across devices
</div>
</a>
<a
class="text-sm relative flex items-center w-full p-4 rounded-lg gap-2 group hover:bg-zinc-50 dark:hover:bg-white/5"
href="#_"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler-external-file-dots size-5 group-hover:text-zinc-500 dark:group-hover:text-zinc-400 text-zinc-500 dark:text-zinc-400"
slot="left-icon"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
<path
d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"
></path>
<path d="M9 14v.01"></path>
<path d="M12 14v.01"></path>
<path d="M15 14v.01"></path>
</svg>
<div
class="text-base font-medium text-zinc-900 dark:text-white group-hover:text-zinc-500 dark:group-hover:text-zinc-400"
>
Create custom documents
</div>
</a>
<a
class="text-sm relative flex items-center w-full p-4 rounded-lg gap-2 group hover:bg-zinc-50 dark:hover:bg-white/5"
href="#_"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler-external-edit size-5 group-hover:text-zinc-500 dark:group-hover:text-zinc-400 text-zinc-500 dark:text-zinc-400"
slot="left-icon"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"
></path>
<path
d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"
></path>
<path d="M16 5l3 3"></path>
</svg>
<div
class="text-base font-medium text-zinc-900 dark:text-white group-hover:text-zinc-500 dark:group-hover:text-zinc-400"
>
Edit your files
</div>
</a>
<a
class="text-sm relative flex items-center w-full p-4 rounded-lg gap-2 group hover:bg-zinc-50 dark:hover:bg-white/5"
href="#_"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler-external-brain size-5 group-hover:text-zinc-500 dark:group-hover:text-zinc-400 text-zinc-500 dark:text-zinc-400"
slot="left-icon"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M15.5 13a3.5 3.5 0 0 0 -3.5 3.5v1a3.5 3.5 0 0 0 7 0v-1.8"
></path>
<path
d="M8.5 13a3.5 3.5 0 0 1 3.5 3.5v1a3.5 3.5 0 0 1 -7 0v-1.8"
></path>
<path d="M17.5 16a3.5 3.5 0 0 0 0 -7h-.5"></path>
<path d="M19 9.3v-2.8a3.5 3.5 0 0 0 -7 0"></path>
<path d="M6.5 16a3.5 3.5 0 0 1 0 -7h.5"></path>
<path d="M5 9.3v-2.8a3.5 3.5 0 0 1 7 0v10"></path>
</svg>
<div
class="text-base font-medium text-zinc-900 dark:text-white group-hover:text-zinc-500 dark:group-hover:text-zinc-400"
>
Organize files
</div>
</a>
</div>
</div>
</div>
</div>
</div>
Features
- Icon + text layout - Clean visual hierarchy with icons
- Smooth transitions - Professional animations
- Click-outside-to-close - Intuitive user experience
- Keyboard navigation - ESC to close, proper focus management
- Smart positioning - Automatically positions below trigger
- Teleport rendering - Prevents clipping in overflow containers
- Dark mode support - Built-in theme compatibility
- Responsive design - Works on all screen sizes
Usage
---
import SimpleFlyout from '/src/components/flyouts/SimpleFlyout.astro'
---
<!-- Basic usage -->
<SimpleFlyout />
<!-- With initial state -->
<SimpleFlyout open={true} />
Customization
Changing Menu Items
Update the <a> elements inside the flyout to match your content:
<a class="text-sm relative flex items-center w-full p-4 rounded-lg gap-2 group hover:bg-zinc-50 dark:hover:bg-white/5" href="#_">
<svg class="icon icon-tabler-your-icon size-5 text-zinc-500 dark:text-zinc-400">
<!-- Your icon SVG -->
</svg>
<h3 class="text-base font-medium text-zinc-900 dark:text-white">
Your menu item
</h3>
</a>
Styling the Button
Modify the button classes to change appearance:
<button class="your-custom-button-classes">
<span>Your button text</span>
<!-- Chevron icon -->
</button>
Adjusting Positioning
The gap variable controls spacing between button and flyout:
gap: 12, // Increase for more spacing
Accessibility
- ARIA attributes - Proper
aria-expandedand role attributes - Keyboard navigation - ESC to close, Tab to navigate items
- Focus management - Proper focus handling for screen readers
- Semantic HTML - Uses appropriate HTML elements for structure
Best Practices
- Use descriptive icons that complement the text
- Ensure sufficient color contrast for accessibility
- Test keyboard navigation thoroughly
- Consider mobile touch interactions
- Keep menu items concise and actionable