Svelte Headless UI

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

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
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
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
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