Menu

Provide the behavior and accessibility implementation for a menu component.

# Features

  • Customization options for side, alignment, offsets, and collision handling.
  • Fully managed focus for seamless navigation.
  • Full keyboard navigation support, including automatic focusing on the first or last item.
  • Typeahead support for quickly focusing items by typing their text.
  • Support for disabled items.

# Dependencies

This module depends on @floating-ui/dom.

# Anatomy

Typically, a menu consists of:

  • Trigger: The element that toggles the menu.
  • Menu: The container for menu items. The menu consists of:
    • Item: An element used to trigger the selection.
    • Separator: An optional element used to visually separate menu items.
<script>
	import { createMenu } from '@grail-ui/svelte';

	const { useTrigger, triggerAttrs, useMenu, menuAttrs, itemAttrs, open } = createMenu();

	const options = [
		{ id: "edit", label: "Edit" },
		{ id: "delete", label: "Delete" },
		{ id: "destroy", label: "Destroy" },
	];
</script>

<button use:useTrigger {...$triggerAttrs}>Actions</button>

{#if $open}
	<ul use:useMenu {...$menuAttrs}>
		{#each options as option (option.id)}
			<li><a href="/" {...$itemAttrs(option.id)}>{option.label}</a></li>
		{/each}
	</ul>
{/if}

# Examples

# Listening to item selection

Pass onSelect to the createMenu function to perform some custom logic when an item is selected. This callback is invoked with the id of the item.

createMenu({
onSelect(id) {
console.log(id);
},
})

# Accessibility

# Keyboard Interactions

Key description
Space Enter
When focus is trigger element, toggles the menu.
Esc
Closes the menu and moves focus to trigger element.
When focus is on trigger, opens the menu and focuses first available item. When focus is on an item, moves focus to the next one.
When focus is on trigger, opens the menu and focuses last available item. When focus is on an item, moves focus to the previous one.
Home
Moves focus to the first element.
End
Moves focus to the last element.

# API

# MenuConfig

Property Description Default
open?
boolean
The open state of the popover when it is initially rendered.
false
positioning?
Partial<PositioningOptions>
Where to place the floating element relative to its reference element.
{ placement: 'bottom' }
portal?
string | HTMLElement | null
Where to "portal" the floating element outside it's initial DOM position. It can be a HTMLElement or a CSS selector that points to an already existing element.
null
onOpenChange?
(value?: boolean) => void | undefined
Event handler called when the open state of the menu changes.
ariaLabel?
string
Defines a string value that labels the current element.
Menu
onSelect?
(value: T) => void | undefined | boolean
Event handler called when an item is selected. Return false to prevent closing of the menu.

# MenuReturn

Property Description
useTrigger
Action<HTMLElement, void>
Action for the trigger element.
triggerAttrs
Readable<Record<string, string | undefined>>
HTML attributes for the trigger element.
useMenu
Action<HTMLElement, void>
Action for the menu element.
menuAttrs
Readable<Record<string, string>>
HTML attributes for the menu element.
itemAttrs
Readable<Function>
HTML attributes for the menu item element.
separatorAttrs
Readable<Record<string, string>>
HTML attributes for the element that is used to visually separate menu items.
open
Writable<boolean>
The controlled open state of the menu.

# PositioningOptions

Property Description Default
strategy?
absolute | fixed
The strategy to use for positioning.
"absolute"
placement?
top | top-start | top-end | right | right-start | right-end | bottom | bottom-start | bottom-end | left | left-start | left-end
The initial placement of the floating element.
"top"
offset?
Object
The offset of the floating element.
gutter?
number
The main axis offset or gap between the reference and floating elements.
5
overflowPadding?
number
The virtual padding around the viewport edges to check for overflow.
8
flip?
boolean
Whether to flip the placement.
true
overlap?
boolean
Whether the floating element can overlap the reference element.
false
sameWidth?
boolean
Whether to make the floating element same width as the reference element.
false
fitViewport?
boolean
Whether the floating element should fit the viewport.
false
boundary?
@floating-ui/dom.Boundary
The overflow boundary of the reference element.