Menu
Basic example
Menus are built using a combination of the Menu
, MenuButton
, MenuItems
, and MenuItem
components:
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
} from "@rgossiaux/svelte-headlessui";
</script>
<Menu>
<MenuButton>More</MenuButton>
<MenuItems>
<MenuItem let:active>
<a href="/account-settings" class:active>Account settings</a>
</MenuItem>
<MenuItem let:active>
<a href="/documentation" class:active>Documentation</a>
</MenuItem>
<MenuItem disabled>
<span class="disabled">Invite a friend (coming soon!)</span>
</MenuItem>
</MenuItems>
</Menu>
Styling
See here for some general notes on styling the components in this library.
Active item
To style the active MenuItem
, you can use the active
slot prop that it provides, which tells you whether or not that menu item is currently focused via the mouse or keyboard. You can use this state to conditionally apply whatever active/focus styles you wish.
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
} from "@rgossiaux/svelte-headlessui";
</script>
<Menu>
<MenuButton>More</MenuButton>
<MenuItems>
<!--Use the `active` slot prop to conditionally style the active item.-->
<MenuItem let:active>
<a href="/account-settings" class={`${active ? "active" : "inactive"}`}
>Account settings</a
>
</MenuItem>
<!-- ... -->
</MenuItems>
</Menu>
If necessary, you can also style the MenuItem
itself by passing a function to class
:
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
} from "@rgossiaux/svelte-headlessui";
</script>
<Menu>
<MenuButton>More</MenuButton>
<MenuItems>
<!--Use the `active` slot prop to conditionally style the active item.-->
<MenuItem class={({ active }) => (active ? "active" : "inactive")}>
Account settings
</MenuItem>
<!-- ... -->
</MenuItems>
</Menu>
Showing/hiding the menu
By default, the MenuItems
instance will be shown and hidden automatically based on the internal open
state tracked by the Menu
component itself.
If you’d rather handle this yourself (perhaps because you need to add an extra wrapper element for one reason or another), you can add a static
prop to the MenuItems
component to tell it to always render, and use the open
slot prop provided by the Menu
to show or hide the menu yourself.
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
} from "@rgossiaux/svelte-headlessui";
</script>
<Menu let:open>
<MenuButton>More</MenuButton>
{#if open}
<!-- Using `static`, `MenuItems` is always rendered and ignores the `open` state. -->
<MenuItems static>
<MenuItem>
<!-- ... -->
</MenuItem>
<!-- ... -->
</MenuItems>
{/if}
</Menu>
You can also choose to have the Menu
merely hide the MenuItems
when the Menu
is closed, instead of removing it from the DOM entirely, by using the unmount
prop. This may be useful for performance reasons if rendering the MenuItems
is expensive.
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
} from "@rgossiaux/svelte-headlessui";
</script>
<Menu>
<MenuButton>More</MenuButton>
<!-- Using `unmount={false}`, the `MenuItems` is kept in the DOM when the `Menu` is closed -->
<MenuItems unmount={false}>
<MenuItem>
<!-- ... -->
</MenuItem>
<!-- ... -->
</MenuItems>
</Menu>
Disabling an item
Use the disabled
prop to disable a MenuItem
. This will make it unselectable via keyboard navigation, and it will be skipped when pressing the up/down arrows.
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
} from "@rgossiaux/svelte-headlessui";
</script>
<Menu>
<MenuButton>More</MenuButton>
<MenuItems>
<!-- ... -->
<!-- This item will be skipped by keyboard navigation -->
<MenuItem disabled>
<span class="disabled">Invite a friend (coming soon!)</span>
</MenuItem>
<!-- ... -->
</MenuItems>
</Menu>
Transitions
To animate the opening and closing of the menu, you can use this library’s Transition component or Svelte’s built-in transition engine. See that page for a comparison.
Using the Transition
component
To use the Transition
component, all you need to do is wrap the MenuItems
in a <Transition>
, and the transition will be applied automatically.
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
Transition,
} from "@rgossiaux/svelte-headlessui";
</script>
<Menu>
<MenuButton>More</MenuButton>
<!-- Example using Tailwind CSS transition classes -->
<Transition
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<MenuItems>
<MenuItem>
<!-- ... -->
</MenuItem>
<!-- ... -->
</MenuItems>
</Transition>
</Menu>
The components in this library communicate with each other, so the Transition will be managed automatically when the Menu is opened/closed. If you require more control over this behavior, you may use a more explicit version:
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
Transition,
} from "@rgossiaux/svelte-headlessui";
</script>
<!-- Use the open slot prop -->
<Menu let:open>
<MenuButton>More</MenuButton>
<!-- Example using Tailwind CSS transition classes -->
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<!-- Mark this as static -->
<MenuItems static>
<MenuItem>
<!-- ... -->
</MenuItem>
<!-- ... -->
</MenuItems>
</Transition>
</Menu>
Using Svelte transitions
The last example above also provides a blueprint for using Svelte transitions:
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
Transition,
} from "@rgossiaux/svelte-headlessui";
import { fade } from "svelte/transition";
</script>
<!-- Use the open slot prop -->
<Menu let:open>
<MenuButton>More</MenuButton>
<!-- Example using Tailwind CSS transition classes -->
{#if open}
<div transition:fade>
<!-- Mark this as static -->
<MenuItems static>
<MenuItem>
<!-- ... -->
</MenuItem>
<!-- ... -->
</MenuItems>
</div>
{/if}
</Menu>
Make sure to use the static
prop, or else the exit transitions won’t work correctly.
Rendering additional content
The accessibility semantics of role=“menu” are fairly strict, and any children of a Menu besides MenuItem components will be automatically hidden from assistive technology to make sure the menu works the way screen reader users expect.
For this reason, rendering any children other than MenuItem components is discouraged, as that content will be inaccessible to people using assistive technology.
If you want to build a dropdown with more flexible content, consider using Popover instead.
When to use a Menu
Menus are best for UI elements that resemble things like the menus you’d find in the title bar of most operating systems. They have specific accessibility semantics, and their content should be restricted to a list of links or buttons. Focus is trapped in an open menu, so you cannot tab through the content or away from the menu. Instead, the arrow keys navigate through a Menu
’s items.
Here’s when you might use other similar components from Headless UI:
- Popover: Popovers are general-purpose floating menus. They appear near the button that triggers them, and you can put arbitrary markup in them like images or non-clickable content. The Tab key navigates the contents of a Popover like it would any other normal markup. They're great for building header nav items with expandable content and flyout panels.
- Disclosure: Disclosures are useful for elements that expand to reveal additional information, like a toggleable FAQ section. They are typically rendered inline and reflow the document when they're shown or hidden.
- Dialog: Dialogs are meant to grab the user's full attention. They typically render a floating panel in the center of the screen, and use a backdrop to dim the rest of the application's contents. They also capture focus and prevent tabbing away from the Dialog's contents until the Dialog is dismissed.
Accessibility notes
Focus management
Clicking the MenuButton
toggles the menu and focuses the MenuItems
component. Focus is trapped within the open menu until Escape is pressed or the user clicks outside the menu. Closing the menu returns focus to the MenuButton
.
Mouse interaction
Clicking a MenuButton
toggles the menu. Clicking anywhere outside of an open menu will close that menu.
Keyboard interaction
Command | Description |
---|---|
<Enter> / <Space> when MenuButton is focused |
Opens menu and focuses first non-disabled item |
<ArrowDown> / <ArrowUp> when MenuButton is focused |
Opens menu and focuses first/last non-disabled item |
<Esc> when menu is open |
Closes any open Menus |
<ArrowDown> / <ArrowUp> when menu is open |
Focuses next/previous non-disabled item |
<Home> / <End> when menu is open |
Focuses first/last non-disabled item |
<Enter> / <Space> when menu is open |
Activates/clicks the current menu item |
<A-Za-z> when menu is open |
Focuses next item that matches keyboard input |
Other
All relevant ARIA attributes are automatically managed.
For a full reference on all accessibility features implemented in Menu
, see the ARIA spec on Menus.
Component API
Menu
Prop | Default | Type | Description |
---|---|---|---|
as |
div |
string |
The element the Menu should render as |
Slot prop | Type | Description |
---|---|---|
open |
boolean |
Whether or not the menu is open |
MenuButton
Prop | Default | Type | Description |
---|---|---|---|
as |
button |
string |
The element the MenuButton should render as |
Slot prop | Type | Description |
---|---|---|
open |
boolean |
Whether or not the menu is open |
MenuItems
Prop | Default | Type | Description |
---|---|---|---|
as |
div |
string |
The element the MenuItems should render as |
static |
false |
boolean |
Whether the element should ignore the internally managed open/closed state |
unmount |
true |
boolean |
Whether the element should be unmounted, instead of just hidden, based on the open/closed state |
Note that static
and unmount
cannot be used together.
Slot prop | Type | Description |
---|---|---|
open |
boolean |
Whether or not the menu is open |
MenuItem
Prop | Default | Type | Description |
---|---|---|---|
as |
a |
string |
The element the MenuItem should render as |
disabled |
false |
boolean |
Whether the item should be disabled for keyboard navigation and ARIA purposes |
Slot prop | Type | Description |
---|---|---|
active |
boolean |
Whether the option is active (using the mouse or keyboard) |
disabled |
boolean |
Whether the item is disabled for keyboard navigation and ARIA purposes |