Menu
Live demo
Menu
Source for this preview
---import {MENU_LABELS} from '../../example-data';---
<article id="menu-demo" class="demo-card menu-card"> <div class="card-top"> <span class="number">C</span><span class="chip">roving focus</span> </div> <h3>Command menu</h3> <p> Arrow keys, looping, and typeahead share one list registry. Try typing “signal”. </p> <div class="card-action"> <floating-root id="menu-root" interactions="click dismiss" floating-role="menu" placement="bottom-start" > <floating-reference> <button class="dark-button"> Open navigator <span aria-hidden="true">⌄</span> </button> </floating-reference> <template slot="content"> <div class="menu-panel"> <div class="menu-heading"> <span>Jump to a field</span> <span class="menu-shortcuts" aria-label="Arrow keys to navigate, Enter to select, Escape to close"> <kbd>↑</kbd><kbd>↓</kbd><kbd>↵</kbd><kbd>Esc</kbd> </span> </div> <floating-list navigation typeahead loop item-selector=".menu-item" > { MENU_LABELS.map((label) => ( <button class="menu-item" role="menuitem" data-fup-close aria-label={label} > <span>{label}</span><kbd>{label.slice(0, 1).toUpperCase()}</kbd> </button> )) } </floating-list> </div> </template> </floating-root> </div> <code><floating-list navigation typeahead item-selector="…" /></code></article>
<script> import { FloatingRootElement, flip, offset, size, shift, transformOrigin, } from '@floating-ui-plus/web-components'; import {initializeExample} from './initialize-example';
initializeExample('menu', (scope) => { const floating = FloatingRootElement.query(scope, '#menu-root'); floating.configure({ middleware: [ offset(8), flip({padding: 18}), shift({padding: 18}), size({ padding: 18, rootBoundary: 'viewport', apply({availableHeight, elements}) { elements.floating.style.setProperty( '--menu-panel-max-height', `${Math.max(0, availableHeight)}px`, ); }, }), transformOrigin({padding: 12}), ], }); });</script><script setup lang="ts">import { FloatingContent, FloatingList, FloatingListItem, FloatingReference, FloatingRoot, autoUpdate, click, dismiss, flip, offset, role, size, shift, transformOrigin,} from '@floating-ui-plus/vue';import {ref} from 'vue';import {MENU_LABELS} from '../../example-data';
const labels = MENU_LABELS;const open = ref(false);const options = { placement: 'bottom-start', middleware: [ offset(8), flip({padding: 18}), shift({padding: 18}), size({ padding: 18, rootBoundary: 'viewport', apply({availableHeight, elements}) { elements.floating.style.setProperty( '--menu-panel-max-height', `${Math.max(0, availableHeight)}px`, ); }, }), transformOrigin({padding: 12}), ], whileElementsMounted: autoUpdate,} as const;const plugins = [ click(), dismiss(), role({role: 'menu'}),];</script>
<template> <article class="vue-demo-card bg-vue-cream"> <div class="vue-card-top"><span class="vue-number">C</span><span>roving focus</span></div> <h3>Command menu</h3> <p>Arrow keys, looping and typeahead share one list registry. Type “signal” after opening.</p> <div class="mt-auto pt-7"> <FloatingRoot v-model:open="open" :options="options" :plugins="plugins"> <FloatingReference class="vue-button vue-button-ink"> Open navigator <span>⌄</span> </FloatingReference> <FloatingList navigation typeahead loop> <FloatingContent class="menu-panel"> <div class="menu-heading"> <span>Jump to a field</span> <span class="menu-shortcuts" aria-label="Arrow keys to navigate, Enter to select, Escape to close"> <kbd>↑</kbd><kbd>↓</kbd><kbd>↵</kbd><kbd>Esc</kbd> </span> </div> <FloatingListItem v-for="label in labels" :key="label" tag="button" :label="label" class="menu-item" role="menuitem" close-on-click > <span>{{ label }}</span><kbd>{{ label.slice(0, 1).toUpperCase() }}</kbd> </FloatingListItem> </FloatingContent> </FloatingList> </FloatingRoot> </div> <code><FloatingList navigation typeahead loop></code> </article></template>The menu’s registry owns active index, roving focus, typeahead labels, and wrapping:
<floating-list navigation typeahead loop item-selector=".menu-item"> <button class="menu-item" role="menuitem" data-fup-close aria-label="Signal"> Signal </button></floating-list>Open the demo and type “signal” to exercise typeahead. Arrow keys move the
active item, Enter selects it, and Escape dismisses the surface. In Vue, use
FloatingList navigation typeahead loop with FloatingListItem and bind the
same active-index model when the application needs it.
The menu root uses click(), dismiss(), role({role: 'menu'}), and
offset(8) → flip() → shift({padding: 18}). data-fup-close sends a normal
close request, so before-close guards still apply.