Skip to content
Brok UI

Loading…

No results

Menus

Open source Core

Action menus — a dropdown on a trigger, a right-click context menu, an application menubar and an inline-expanding nested menu.

Version
v1.4.2
Stability
stable
License
MIT
Related
Dropdown Menu
Context Menu
Nested Menu

Menubar — A horizontal bar of dropdown menus for application commands.

Preview

previews.components.menubar.default.blade.php Blade
<x-ui.menubar>
    <x-ui.menubar.menu>
        <x-ui.menubar.trigger>{{ __('File') }}</x-ui.menubar.trigger>
        <x-ui.menubar.content>
            <x-ui.menubar.item>{{ __('New Tab') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('New Window') }}</x-ui.menubar.item>
            <x-ui.menubar.separator />
            <x-ui.menubar.item>{{ __('Print') }}</x-ui.menubar.item>
        </x-ui.menubar.content>
    </x-ui.menubar.menu>

    <x-ui.menubar.menu>
        <x-ui.menubar.trigger>{{ __('Edit') }}</x-ui.menubar.trigger>
        <x-ui.menubar.content>
            <x-ui.menubar.item>{{ __('Undo') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('Redo') }}</x-ui.menubar.item>
            <x-ui.menubar.separator />
            <x-ui.menubar.item>{{ __('Cut') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('Copy') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('Paste') }}</x-ui.menubar.item>
        </x-ui.menubar.content>
    </x-ui.menubar.menu>

    <x-ui.menubar.menu>
        <x-ui.menubar.trigger>{{ __('View') }}</x-ui.menubar.trigger>
        <x-ui.menubar.content>
            <x-ui.menubar.label>{{ __('Appearance') }}</x-ui.menubar.label>
            <x-ui.menubar.item>{{ __('Toggle Fullscreen') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('Hide Sidebar') }}</x-ui.menubar.item>
            <x-ui.menubar.separator />
            <x-ui.menubar.item>{{ __('Reload') }}</x-ui.menubar.item>
        </x-ui.menubar.content>
    </x-ui.menubar.menu>
</x-ui.menubar>

Installation

terminal
php artisan ui:add menubar

Note

This component ships an Alpine behavior module at resources/js/ui/menubar.js. Import it once from your bundle so it registers on alpine:init:

resources/js/ui/index.js JS
import './menubar.js';

Registry contract

php artisan ui:add menubar writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/menubar.blade.php
  • blade resources/views/components/ui/menubar/menu.blade.php
  • blade resources/views/components/ui/menubar/trigger.blade.php
  • blade resources/views/components/ui/menubar/content.blade.php
  • blade resources/views/components/ui/menubar/item.blade.php
  • blade resources/views/components/ui/menubar/separator.blade.php
  • blade resources/views/components/ui/menubar/label.blade.php
  • js resources/js/ui/menubar.js
Registry dependencies
dropdown
Packages
composer: jml/brok:^0.2
npm: alpinejs

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

menubar.md
# Brok UI: Menubar (`menubar`)

A horizontal bar of dropdown menus for application commands.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add menubar
```

## Usage

```blade
<x-ui.menubar>
    <x-ui.menubar.menu>
        <x-ui.menubar.trigger>{{ __('File') }}</x-ui.menubar.trigger>
        <x-ui.menubar.content>
            <x-ui.menubar.item>{{ __('New Tab') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('New Window') }}</x-ui.menubar.item>
            <x-ui.menubar.separator />
            <x-ui.menubar.item>{{ __('Print') }}</x-ui.menubar.item>
        </x-ui.menubar.content>
    </x-ui.menubar.menu>

    <x-ui.menubar.menu>
        <x-ui.menubar.trigger>{{ __('Edit') }}</x-ui.menubar.trigger>
        <x-ui.menubar.content>
            <x-ui.menubar.item>{{ __('Undo') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('Redo') }}</x-ui.menubar.item>
            <x-ui.menubar.separator />
            <x-ui.menubar.item>{{ __('Cut') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('Copy') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('Paste') }}</x-ui.menubar.item>
        </x-ui.menubar.content>
    </x-ui.menubar.menu>

    <x-ui.menubar.menu>
        <x-ui.menubar.trigger>{{ __('View') }}</x-ui.menubar.trigger>
        <x-ui.menubar.content>
            <x-ui.menubar.label>{{ __('Appearance') }}</x-ui.menubar.label>
            <x-ui.menubar.item>{{ __('Toggle Fullscreen') }}</x-ui.menubar.item>
            <x-ui.menubar.item>{{ __('Hide Sidebar') }}</x-ui.menubar.item>
            <x-ui.menubar.separator />
            <x-ui.menubar.item>{{ __('Reload') }}</x-ui.menubar.item>
        </x-ui.menubar.content>
    </x-ui.menubar.menu>
</x-ui.menubar>
```

## Use when

- Use for secondary actions when compact access is more important than constant visibility.
- Building a desktop-style application command bar with several grouped menus (File, Edit, View, and similar).
- Needing keyboard and hover behavior that matches native OS menu bars, where opening one menu makes hovering siblings switch between them.

## Avoid when

- Do not hide the most frequent or highest-value actions in a menu when discoverability matters.
- A single dropdown menu covers the need; use dropdown directly instead of wrapping it in a menubar.
- The target is a mobile or narrow layout where a persistent horizontal bar does not fit; use a single dropdown or a sheet-based menu.

## Anti-patterns

- Hiding primary actions in menus

## Rules

- Use the `<brok:menubar>` tag (or `<x-ui.menubar>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/menubar
- Registry JSON (files, props, contract): https://brokui.dev/r/open/menubar.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

API

manifest knowledge + registry-derived coverage

Props

No documented props. Pass normal Blade attributes through the component root.

Slots

  • default — menubar.menu blocks, each composing a trigger and its dropdown content.
  • x-ui.menubar.menu — Installed subcomponent from the registry item.
  • x-ui.menubar.trigger — Installed subcomponent from the registry item.
  • x-ui.menubar.content — Installed subcomponent from the registry item.
  • x-ui.menubar.item — Installed subcomponent from the registry item.
  • x-ui.menubar.separator — Installed subcomponent from the registry item.
  • x-ui.menubar.label — Installed subcomponent from the registry item.

Data slots

Stable hooks for CSS overrides and browser tests.

menubar menubar-content menubar-item menubar-label menubar-menu menubar-separator menubar-trigger

Behavior

  • Clicking a trigger toggles its menu; once any menu in the bar is open, hovering another trigger switches the open menu to it, matching native app menu bars.
  • Selecting an item closes every open menu in the bar, not just the one it belongs to.
  • Each menu's content reuses dropdown's content and item components, so it shares the same roving focus and type-ahead keyboard behavior.
  • Each panel is dropdown.content in anchored mode: teleported to the body and placed against its trigger by the shared overlay-position.js engine, so an overflow-clipping ancestor never cuts it off and it flips above the trigger at the viewport edge (data-side reports the side).
  • Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
  • Declares registry capability flags: a11y, interactive, responsive, rtl, darkMode, localized.

Guidance

Secondary action menu

Provide compact access to secondary actions.

Use when

  • Use for secondary actions when compact access is more important than constant visibility.
  • Building a desktop-style application command bar with several grouped menus (File, Edit, View, and similar).
  • Needing keyboard and hover behavior that matches native OS menu bars, where opening one menu makes hovering siblings switch between them.

Avoid when

  • Do not hide the most frequent or highest-value actions in a menu when discoverability matters.
  • A single dropdown menu covers the need; use dropdown directly instead of wrapping it in a menubar.
  • The target is a mobile or narrow layout where a persistent horizontal bar does not fit; use a single dropdown or a sheet-based menu.

Use instead

  • Visible button for frequent actions
  • Inline action bar

Anti-patterns

  • Hiding primary actions in menus
Anatomy
root menubar-content menubar-item menubar-label menubar-menu menubar-separator menubar-trigger
Theming hooks
menu

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
Tab Enter Escape ArrowUp ArrowDown ArrowLeft ArrowRight Home End
Focus
managed
  • Triggers expose role="menuitem" with aria-haspopup and a live aria-expanded, and only the active trigger is tab-stoppable at a time.
  • Arrow-key and Escape behavior inside each open menu matches the dropdown menu pattern.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Needs wire:key

Add a stable wire:key when Livewire can reorder this interactive component.

livewire-component.blade.php Blade
<div wire:key="menubar-{{ $record->id }}">
    <x-ui.menubar>
        <x-ui.menubar.menu>
            <x-ui.menubar.trigger>{{ __('File') }}</x-ui.menubar.trigger>
            <x-ui.menubar.content>
                <x-ui.menubar.item>{{ __('New Tab') }}</x-ui.menubar.item>
                <x-ui.menubar.item>{{ __('New Window') }}</x-ui.menubar.item>
                <x-ui.menubar.separator />
                <x-ui.menubar.item>{{ __('Print') }}</x-ui.menubar.item>
            </x-ui.menubar.content>
        </x-ui.menubar.menu>
    
        <x-ui.menubar.menu>
            <x-ui.menubar.trigger>{{ __('Edit') }}</x-ui.menubar.trigger>
            <x-ui.menubar.content>
                <x-ui.menubar.item>{{ __('Undo') }}</x-ui.menubar.item>
                <x-ui.menubar.item>{{ __('Redo') }}</x-ui.menubar.item>
                <x-ui.menubar.separator />
                <x-ui.menubar.item>{{ __('Cut') }}</x-ui.menubar.item>
                <x-ui.menubar.item>{{ __('Copy') }}</x-ui.menubar.item>
                <x-ui.menubar.item>{{ __('Paste') }}</x-ui.menubar.item>
            </x-ui.menubar.content>
        </x-ui.menubar.menu>
    
        <x-ui.menubar.menu>
            <x-ui.menubar.trigger>{{ __('View') }}</x-ui.menubar.trigger>
            <x-ui.menubar.content>
                <x-ui.menubar.label>{{ __('Appearance') }}</x-ui.menubar.label>
                <x-ui.menubar.item>{{ __('Toggle Fullscreen') }}</x-ui.menubar.item>
                <x-ui.menubar.item>{{ __('Hide Sidebar') }}</x-ui.menubar.item>
                <x-ui.menubar.separator />
                <x-ui.menubar.item>{{ __('Reload') }}</x-ui.menubar.item>
            </x-ui.menubar.content>
        </x-ui.menubar.menu>
    </x-ui.menubar>
</div>

Validation

Validation support: native. Keep the error message connected with aria-describedby.

livewire-form.blade.php Blade
<form wire:submit="save" class="space-y-2">
    <brok:menubar
        wire:model="value"
        :aria-invalid="$errors->has('value') ? 'true' : 'false'"
        aria-describedby="value-error"
    />

    @error('value')
        <p id="value-error" role="alert">{{ $message }}</p>
    @enderror

    <brok:button type="submit" wire:loading.attr="disabled">
        <span wire:loading.remove>Save</span>
        <span wire:loading>Saving…</span>
    </brok:button>
</form>

Source

The exact, editable files ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/menubar.blade.php Blade
{{--
    Menubar root (interactive). A single uiMenubar Alpine component tracks which
    menu is open. Arrow keys move roving focus across triggers; Escape and
    outside click close all menus. Each menu's panel is the anchored dropdown
    panel (teleported to <body>), so the outside-click check skips clicks that
    land inside an open panel.
--}}
<div
    x-data="uiMenubar()"
    data-slot="menubar"
    role="menubar"
    @keydown="onKey($event)"
    @click.outside="if (!$event.target.closest('[data-slot=menubar-content]')) closeAll(false)"
    {{ $attributes->merge(['class' => 'flex items-center gap-1 rounded-md border border-border bg-background p-1']) }}
>
    {{ $slot }}
</div>
resources/views/components/ui/menubar/menu.blade.php Blade
{{--
    Menubar menu. Nested x-data provides a stable menuId via $id while the parent
    uiMenubar methods (open/toggle/isOpen/closeAll) remain in scope.
--}}
<div
    data-slot="menubar-menu"
    x-data="{ menuId: $id('menubar-menu') }"
    {{ $attributes->merge(['class' => 'relative']) }}
>
    {{ $slot }}
</div>
resources/views/components/ui/menubar/trigger.blade.php Blade
{{--
    Menubar trigger. Toggles its menu on click; once any menu is open, hovering a
    trigger switches to it (app-menu-bar behavior).
--}}
<button
    type="button"
    data-slot="menubar-trigger"
    role="menuitem"
    aria-haspopup="menu"
    x-bind:data-menu-id="menuId"
    x-bind:aria-controls="menuId"
    :aria-expanded="isOpen(menuId)"
    :tabindex="triggerTabIndex(menuId)"
    :data-state="isOpen(menuId) ? 'open' : 'closed'"
    @click="toggle(menuId)"
    @focus="rememberTrigger(menuId)"
    @mouseenter="anyOpen() && open(menuId)"
    {{ $attributes->merge(['class' => 'rounded-sm px-3 py-1 text-sm font-medium text-foreground outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground']) }}
>
    {{ $slot }}
</button>
resources/views/components/ui/menubar/content.blade.php Blade
{{--
    Menubar content — the anchored dropdown panel under the menubar's name,
    shown while its menu is open and placed against its trigger by menubar.js
    through the shared `overlay-position.js` engine (start-aligned, RTL-aware,
    flipped above the trigger at the viewport edge). `data-menu-id` is how
    menubar.js finds the teleported panel for a trigger.
--}}
<x-ui.dropdown.content
    :anchored="true"
    anchor="trigger"
    show="isOpen(menuId)"
    data-slot="menubar-content"
    x-bind:id="menuId"
    x-bind:data-menu-id="menuId"
    {{ $attributes }}
>{{ $slot }}</x-ui.dropdown.content>
resources/views/components/ui/menubar/item.blade.php Blade
{{--
    Menubar item — a dropdown row under the menubar's name. Selecting an item
    closes all menus (see onItemClick in menubar.js).
--}}
<x-ui.dropdown.item data-slot="menubar-item" {{ $attributes }}>{{ $slot }}</x-ui.dropdown.item>
resources/views/components/ui/menubar/separator.blade.php Blade
{{-- Menubar separator — a dropdown separator under the menubar's name. --}}
<x-ui.dropdown.separator data-slot="menubar-separator" {{ $attributes }} />
resources/views/components/ui/menubar/label.blade.php Blade
{{-- Menubar label — a dropdown label under the menubar's name. --}}
<x-ui.dropdown.label data-slot="menubar-label" {{ $attributes }}>{{ $slot }}</x-ui.dropdown.label>
resources/js/ui/menubar.js JS
/**
 * Menubar behavior (interactive app-menu-bar).
 *
 * A single uiMenubar root tracks which menu is open by its Alpine $id. Clicking
 * a trigger toggles its menu; while any menu is open, hovering another trigger
 * switches to it. ArrowLeft/ArrowRight (RTL-aware), Home and End move a roving
 * focus across triggers. Escape and outside click close all menus.
 *
 * Each menu's panel is the anchored dropdown panel (teleported to <body>,
 * `position: fixed`), placed against its trigger by the shared
 * `overlay-position.js` engine: it flips above the trigger when it would
 * overflow the viewport below and reports the side through `data-side`.
 * The tracker re-places the open panel on resize/scroll and is torn down when
 * every menu is closed and in `destroy()`.
 */
import { placeOverlay, trackOverlayPosition } from './overlay-position.js';

document.addEventListener('alpine:init', () => {
    window.Alpine.data('uiMenubar', () => ({
        active: null,
        focusedId: null,
        activeTrigger: null,
        // Teardown for the resize/scroll tracker attached while a menu is open.
        _untrack: null,
        // The menubar root. Handlers on a teleported panel run with `$el` set
        // to the panel, so the bar is pinned here for trigger lookups.
        _bar: null,

        init() {
            this._bar = this.$el;
            // Every way a menu becomes active (click, hover switch, arrow
            // keys) flows through `active`, so placement follows it: the
            // open panel is placed once laid out and tracked until all close.
            this.$watch('active', (id) => {
                if (!id) {
                    this.teardownReposition();
                    return;
                }
                this.whenShown(id, () => {
                    this.reposition();
                    if (!this._untrack) {
                        this._untrack = trackOverlayPosition(() => this.reposition());
                    }
                });
            });
        },

        /**
         * Run `callback` once the panel for `id` is laid out. The panel's
         * enter transition shows it a frame after `active` changes, and a
         * sibling's leave transition can release Alpine's held ticks before
         * that frame, so a bare $nextTick may still see `display: none`.
         */
        whenShown(id, callback, attempts = 5) {
            const ready = () => this.active === id && this.menuPanel(id)?.getClientRects().length > 0;
            const attempt = (left) => {
                if (this.active !== id) return;
                if (ready() || left === 0) {
                    callback();
                    return;
                }
                window.requestAnimationFrame(() => attempt(left - 1));
            };
            this.$nextTick(() => attempt(attempts));
        },

        open(id, focus = null) {
            this.active = id;
            this.rememberTrigger(id);
            if (focus) {
                this.whenShown(id, () => {
                    const items = this.menuItems(id);
                    (focus === 'last' ? items.at(-1) : items[0])?.focus();
                });
            }
        },

        close(id) {
            if (this.active === id) {
                this.active = null;
            }
        },

        /** Place the open menu's panel against its trigger (4px gap, as before). */
        reposition() {
            const id = this.active;
            const panel = this.menuPanel(id);
            const trigger = this.triggerFor(id);
            if (!id || !panel || !trigger) {
                return;
            }
            placeOverlay(panel, trigger.getBoundingClientRect(), { gap: 4 });
        },

        teardownReposition() {
            if (this._untrack) {
                this._untrack();
                this._untrack = null;
            }
        },

        closeAndFocus() {
            this.closeAll(true);
        },

        // A click on a plain menu item chooses it: the consumer's own handler
        // on the item has already run by the time the click bubbles here.
        onItemClick(event) {
            const item = event.target.closest('[data-slot="menubar-item"]');
            if (!item || item.dataset.disabled === 'true' || event.defaultPrevented) return;
            this.closeAll(true);
        },

        toggle(id) {
            if (this.active === id) {
                this.closeAll(false);
            } else {
                this.open(id);
            }
        },

        isOpen(id) {
            return this.active === id;
        },

        anyOpen() {
            return this.active !== null;
        },

        closeAll(restoreFocus = false) {
            const trigger = this.activeTrigger || this.triggerFor(this.active);
            this.active = null;
            if (restoreFocus && trigger?.isConnected) {
                this.$nextTick(() => trigger.focus());
            }
        },

        triggers() {
            const bar = this._bar || this.$el;
            return Array.from(
                bar.querySelectorAll('[data-slot="menubar-trigger"]:not([disabled]):not([aria-disabled="true"])')
            ).filter((trigger) => trigger.getClientRects().length > 0);
        },

        triggerFor(id) {
            return this.triggers().find((trigger) => trigger.dataset.menuId === id) || null;
        },

        rememberTrigger(id) {
            this.focusedId = id;
            this.activeTrigger = this.triggerFor(id);
        },

        triggerTabIndex(id) {
            const triggers = this.triggers();
            const current = this.focusedId || triggers[0]?.dataset.menuId;
            return current === id ? 0 : -1;
        },

        // The panel is teleported to <body>, so it is looked up by its menu id
        // rather than under the menubar root.
        menuPanel(id) {
            return id ? document.querySelector(`[data-slot="menubar-content"][data-menu-id="${id}"]`) : null;
        },

        menuItems(id) {
            const content = this.menuPanel(id);
            if (!content) {
                return [];
            }
            return Array.from(content.querySelectorAll('[role="menuitem"]:not([disabled]):not([aria-disabled="true"])'))
                .filter((item) => item.getClientRects().length > 0);
        },

        focusTriggerAt(index, keepOpen = false, focusMenu = false) {
            const triggers = this.triggers();
            if (triggers.length === 0) {
                return;
            }
            const next = (index + triggers.length) % triggers.length;
            const trigger = triggers[next];
            const id = trigger.dataset.menuId;
            this.rememberTrigger(id);
            if (keepOpen) {
                this.active = id;
            }
            trigger.focus();
            if (focusMenu) {
                this.whenShown(id, () => this.menuItems(id)[0]?.focus());
            }
        },

        onKey(e) {
            const t = this.triggers();
            const trigger = e.target.closest('[data-slot="menubar-trigger"]');
            const item = e.target.closest('[role="menuitem"]:not([data-slot="menubar-trigger"])');
            if (!trigger && !item) {
                return;
            }

            const rtl = getComputedStyle(this._bar || this.$el).direction === 'rtl';
            let key = e.key;
            if (rtl && key === 'ArrowLeft') {
                key = 'ArrowRight';
            } else if (rtl && key === 'ArrowRight') {
                key = 'ArrowLeft';
            }

            if (trigger) {
                const cur = t.indexOf(trigger);
                const id = trigger.dataset.menuId;
                switch (key) {
                    case 'ArrowRight':
                        e.preventDefault();
                        this.focusTriggerAt(cur + 1, this.anyOpen());
                        return;
                    case 'ArrowLeft':
                        e.preventDefault();
                        this.focusTriggerAt(cur - 1, this.anyOpen());
                        return;
                    case 'Home':
                        e.preventDefault();
                        this.focusTriggerAt(0, this.anyOpen());
                        return;
                    case 'End':
                        e.preventDefault();
                        this.focusTriggerAt(t.length - 1, this.anyOpen());
                        return;
                    case 'ArrowDown':
                    case 'Enter':
                    case ' ':
                        e.preventDefault();
                        this.open(id, 'first');
                        return;
                    case 'ArrowUp':
                        e.preventDefault();
                        this.open(id, 'last');
                        return;
                    case 'Escape':
                        e.preventDefault();
                        this.closeAll(true);
                        return;
                    default:
                        return;
                }
            }

            const content = item.closest('[data-slot="menubar-content"]');
            const id = content?.dataset.menuId;
            const items = this.menuItems(id);
            const cur = items.indexOf(item);
            switch (key) {
                case 'ArrowDown':
                    e.preventDefault();
                    items[(cur + 1) % items.length]?.focus();
                    break;
                case 'ArrowUp':
                    e.preventDefault();
                    items[(cur - 1 + items.length) % items.length]?.focus();
                    break;
                case 'Home':
                    e.preventDefault();
                    items[0]?.focus();
                    break;
                case 'End':
                    e.preventDefault();
                    items.at(-1)?.focus();
                    break;
                case 'ArrowRight':
                case 'ArrowLeft': {
                    e.preventDefault();
                    const triggerIndex = t.indexOf(this.triggerFor(id));
                    this.focusTriggerAt(triggerIndex + (key === 'ArrowRight' ? 1 : -1), true, true);
                    break;
                }
                case 'Escape':
                    e.preventDefault();
                    this.closeAll(true);
                    break;
                case 'Tab':
                    this.closeAll(false);
                    break;
            }
        },

        destroy() {
            this.teardownReposition();
        },
    }));
});

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.4.2
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0