Skip to content

Menu

Experimental Alpha

This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.

A menu (dropdown menu or context menu) is a popover component that displays a list of actions or options when triggered. Menus provide a compact way to present choices without cluttering the interface.

Examples

Vue
Lit
React
Live Preview

Basic Menu

Menu EditCopyPasteDelete

Menu Alignment

The menu-align prop controls horizontal alignment. Use menu-align="right" when the menu button is near the right edge of the viewport.

Left Aligned Option 1Option 2Option 3 Right Aligned Option 1Option 2Option 3

Menu with Links

Navigation HomeAboutContact External Link

Disabled Button

Disabled Menu Item 1Item 2

Menu with Disabled Items

Mixed States Enabled ItemDisabled ItemAnother EnabledAnother Disabled

Complex Menu (File Menu)

File NewOpen...Open RecentSaveSave As...ExportImportPrintClose

Menu with Sections

User Menu
ProfileSettingsHelpSend FeedbackLogout

Menu with Icon Button

The following examples show using an icon as the trigger. The menu-variant="icon" removes the chevron and provides a minimal button container for the icon. You can also use an icon with the default chevron trigger as we see on the right.

Large

SettingsProfileLogoutSettingsProfileLogout

Medium

SettingsProfileLogoutSettingsProfileLogout

Small

SettingsProfileLogoutSettingsProfileLogout

Monochrome Variant

Monochrome Menu Option 1Option 2 (Selected)Option 3

Event Handling

Event Test Option 1Option 2Option 3

Menu Types: Navigation vs Selection

The type prop controls selection behavior. Use type="default" (the default) for navigation menus where selection is transient, and type="single-select" for menus where selection should persist (like filters or sorting).

Navigation Menu (type="default")

Selection clears when menu closes. Use for navigation and actions.

User Menu ProfileSettingsLogout

Selection Menu (type="single-select")

Selection persists when menu closes. Use for filters, sorting, etc.

Sort by DateNameSize

Additional Gutter

The additional-gutter prop adds extra vertical spacing beyond the trigger button's height when positioning the menu. This is useful when the menu button is within a taller container (like a header) and you need the menu to clear that container.

This menu has additional-gutter="20px"

Menu with Extra Gutter Option 1Option 2Option 3

Responsive Hidden Items (checkHiddenItems)

The check-hidden-items prop enables the menu to skip items that are hidden via CSS (like responsive media queries). This is useful when you wrap menu items in responsive containers but want keyboard navigation to work correctly.

Performance Note: Enabling this feature checks computed styles on every keyboard navigation, which has a performance cost. Only enable it if you're using CSS-based hiding. For better performance, prefer conditional rendering (v-if) instead.

Try resizing your browser: Desktop items are hidden on mobile (<768px), mobile items are hidden on desktop. Keyboard navigation skips hidden items.

Responsive Menu
Desktop Item 1 Desktop Item 2
Mobile Item 1 Mobile Item 2
Always Visible

Recommended: Conditional Rendering (Better Performance)

Instead of using check-hidden-items, you can achieve the same result with better performance by using Vue's conditional rendering (v-if). This removes hidden items from the DOM entirely.

Current viewport: Desktop

Conditional Menu Desktop Item 1 Desktop Item 2 Always Visible

Dynamic Icon Switching

The menu button exposes a data-menu-open attribute that changes based on the menu state. You can use this with CSS to dynamically switch icons when the menu opens/closes.

HomeAboutContactLogout

Click the icon to see it change from ☰ to ✕

CSS Shadow Parts Customization

Custom Menu Option 1Option 2Option 3
View Vue Code
<template>
  <section>
    <div class="mbe4">
      <h2>Basic Menu</h2>
    </div>
    <div class="stacked mbe4">
      <VueMenu menu-aria-label="Menu options">
        Menu
        <template #menu>
          <VueMenuItem value="edit">Edit</VueMenuItem>
          <VueMenuItem value="copy">Copy</VueMenuItem>
          <VueMenuItem value="paste">Paste</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem value="delete">Delete</VueMenuItem>
        </template>
      </VueMenu>
    </div>

    <div class="mbe4">
      <h2>Menu Alignment</h2>
      <p class="mbe4">
        The <code>menu-align</code> prop controls horizontal alignment. Use <code>menu-align="right"</code> when the menu button is near the right edge of the viewport.
      </p>
    </div>
    <div class="stacked mbe4">
      <div
        class="flex items-center"
        style="gap: 1rem; justify-content: space-between;"
      >
        <VueMenu
          menu-align="left"
          menu-aria-label="Left-aligned menu"
        >
          Left Aligned
          <template #menu>
            <VueMenuItem value="option1">Option 1</VueMenuItem>
            <VueMenuItem value="option2">Option 2</VueMenuItem>
            <VueMenuItem value="option3">Option 3</VueMenuItem>
          </template>
        </VueMenu>
        <VueMenu
          menu-align="right"
          menu-aria-label="Right-aligned menu"
        >
          Right Aligned
          <template #menu>
            <VueMenuItem value="option1">Option 1</VueMenuItem>
            <VueMenuItem value="option2">Option 2</VueMenuItem>
            <VueMenuItem value="option3">Option 3</VueMenuItem>
          </template>
        </VueMenu>
      </div>
    </div>

    <div class="mbe4">
      <h2>Menu with Links</h2>
    </div>
    <div class="stacked mbe4">
      <VueMenu menu-aria-label="Navigation menu">
        Navigation
        <template #menu>
          <VueMenuItem
            value="home"
            href="#home"
          >Home</VueMenuItem>
          <VueMenuItem
            value="about"
            href="#about"
          >About</VueMenuItem>
          <VueMenuItem
            value="contact"
            href="#contact"
          >Contact</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem
            value="external"
            href="https://example.com"
            target="_blank"
          >
            External Link
          </VueMenuItem>
        </template>
      </VueMenu>
    </div>

    <div class="mbe4">
      <h2>Disabled Button</h2>
    </div>
    <div class="stacked mbe4">
      <VueMenu
        disabled
        menu-aria-label="Disabled menu"
      >
        Disabled Menu
        <template #menu>
          <VueMenuItem value="item1">Item 1</VueMenuItem>
          <VueMenuItem value="item2">Item 2</VueMenuItem>
        </template>
      </VueMenu>
    </div>

    <div class="mbe4">
      <h2>Menu with Disabled Items</h2>
    </div>
    <div class="stacked mbe4">
      <VueMenu menu-aria-label="Menu with disabled items">
        Mixed States
        <template #menu>
          <VueMenuItem value="enabled1">Enabled Item</VueMenuItem>
          <VueMenuItem
            value="disabled1"
            :disabled="true"
          >Disabled Item</VueMenuItem>
          <VueMenuItem value="enabled2">Another Enabled</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem
            value="disabled2"
            :disabled="true"
          >Another Disabled</VueMenuItem>
        </template>
      </VueMenu>
    </div>

    <div class="mbe4">
      <h2>Complex Menu (File Menu)</h2>
    </div>
    <div class="stacked mbe4">
      <VueMenu menu-aria-label="File menu">
        File
        <template #menu>
          <VueMenuItem value="new">New</VueMenuItem>
          <VueMenuItem value="open">Open...</VueMenuItem>
          <VueMenuItem value="recent">Open Recent</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem value="save">Save</VueMenuItem>
          <VueMenuItem value="save-as">Save As...</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem value="export">Export</VueMenuItem>
          <VueMenuItem value="import">Import</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem value="print">Print</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem value="close">Close</VueMenuItem>
        </template>
      </VueMenu>
    </div>

    <div class="mbe4">
      <h2>Menu with Sections</h2>
    </div>
    <div class="stacked mbe4">
      <VueMenu menu-aria-label="User menu">
        <div class="flex-inline items-center">
          <User
            :size="16"
            class="mie2"
          />
          User Menu
        </div>
        <template #menu>
          <VueMenuItem value="profile">Profile</VueMenuItem>
          <VueMenuItem value="settings">Settings</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem value="help">Help</VueMenuItem>
          <VueMenuItem value="feedback">Send Feedback</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem value="logout">Logout</VueMenuItem>
        </template>
      </VueMenu>
    </div>

    <div class="mbe4">
      <h2>Menu with Icon Button</h2>
      <p class="mbe4">
        The following examples show using an icon as the trigger. The <code>menu-variant="icon"</code> removes the chevron and provides a minimal button container for the icon. You can also use an
        icon with the default chevron trigger as we see on the right.
      </p>
    </div>
    <div class="mbe4">
      <h3>Large</h3>
      <div
        class="flex-inline items-center"
        style="gap: 1rem;"
      >
        <!-- Icon only -->
        <VueMenu
          menu-variant="icon"
          ghost
          menu-aria-label="More options menu"
        >
          <Menu :size="24" />
          <template #menu>
            <VueMenuItem value="settings">Settings</VueMenuItem>
            <VueMenuItem value="profile">Profile</VueMenuItem>
            <VueMenuSeparator />
            <VueMenuItem value="logout">Logout</VueMenuItem>
          </template>
        </VueMenu>
        <!-- Icon with chevron -->
        <VueMenu menu-aria-label="More options menu">
          <Menu :size="24" />
          <template #menu>
            <VueMenuItem value="settings">Settings</VueMenuItem>
            <VueMenuItem value="profile">Profile</VueMenuItem>
            <VueMenuSeparator />
            <VueMenuItem value="logout">Logout</VueMenuItem>
          </template>
        </VueMenu>
      </div>
    </div>
    <div class="mbe4">
      <h3>Medium</h3>
      <div
        class="flex-inline items-center"
        style="gap: 1rem;"
      >
        <!-- Icon only -->
        <VueMenu
          menu-variant="icon"
          ghost
          size="sm"
          menu-aria-label="More options menu"
        >
          <Menu :size="18" />
          <template #menu>
            <VueMenuItem value="settings">Settings</VueMenuItem>
            <VueMenuItem value="profile">Profile</VueMenuItem>
            <VueMenuSeparator />
            <VueMenuItem value="logout">Logout</VueMenuItem>
          </template>
        </VueMenu>
        <!-- Icon with chevron -->
        <VueMenu
          size="sm"
          menu-aria-label="More options menu"
        >
          <Menu :size="18" />
          <template #menu>
            <VueMenuItem value="settings">Settings</VueMenuItem>
            <VueMenuItem value="profile">Profile</VueMenuItem>
            <VueMenuSeparator />
            <VueMenuItem value="logout">Logout</VueMenuItem>
          </template>
        </VueMenu>
      </div>
    </div>
    <div class="mbe4">
      <h3>Small</h3>
      <div
        class="flex-inline items-center"
        style="gap: 1rem;"
      >
        <!-- Icon only -->
        <VueMenu
          menu-variant="icon"
          ghost
          size="x-sm"
          menu-aria-label="More options menu"
        >
          <Menu :size="14" />
          <template #menu>
            <VueMenuItem value="settings">Settings</VueMenuItem>
            <VueMenuItem value="profile">Profile</VueMenuItem>
            <VueMenuSeparator />
            <VueMenuItem value="logout">Logout</VueMenuItem>
          </template>
        </VueMenu>
        <!-- Icon with chevron -->
        <VueMenu
          size="x-sm"
          menu-aria-label="More options menu"
        >
          <Menu :size="14" />
          <template #menu>
            <VueMenuItem value="settings">Settings</VueMenuItem>
            <VueMenuItem value="profile">Profile</VueMenuItem>
            <VueMenuSeparator />
            <VueMenuItem value="logout">Logout</VueMenuItem>
          </template>
        </VueMenu>
      </div>
    </div>

    <div class="mbe4">
      <h2>Monochrome Variant</h2>
    </div>
    <div class="stacked mbe4">
      <VueMenu
        menu-aria-label="Monochrome menu"
        button-variant="monochrome"
      >
        Monochrome Menu
        <template #menu>
          <VueMenuItem
            value="option1"
            variant="monochrome"
          >Option 1</VueMenuItem>
          <VueMenuItem
            value="option2"
            variant="monochrome"
            :selected="true"
          >Option 2 (Selected)</VueMenuItem>
          <VueMenuItem
            value="option3"
            variant="monochrome"
          >Option 3</VueMenuItem>
        </template>
      </VueMenu>
    </div>

    <div class="mbe4">
      <h2>Event Handling</h2>
    </div>
    <div class="stacked mbe4">
      <div
        class="flex-inline items-center"
        :style="{gap: '10px'}"
      >
        <VueMenu
          menu-aria-label="Event testing menu"
          @menu-open="handleMenuOpen"
          @menu-close="handleMenuClose"
          @menu-select="handleMenuSelect"
        >
          Event Test
          <template #menu>
            <VueMenuItem value="option1">Option 1</VueMenuItem>
            <VueMenuItem value="option2">Option 2</VueMenuItem>
            <VueMenuSeparator />
            <VueMenuItem value="option3">Option 3</VueMenuItem>
          </template>
        </VueMenu>
        <p v-if="lastEvent">
          Last event: <strong>{{ lastEvent }}</strong>
        </p>
      </div>
    </div>
    <div class="mbe4">
      <h2>Menu Types: Navigation vs Selection</h2>
      <p class="mbe4">
        The <code>type</code> prop controls selection behavior. Use <code>type="default"</code> (the default) for navigation menus where selection is transient, and <code>type="single-select"</code> for menus where selection should persist (like filters or sorting).
      </p>
    </div>
    <div class="stacked mbe4">
      <div
        class="flex items-start"
        style="gap: 2rem; flex-wrap: wrap;"
      >
        <div>
          <h3 class="mbe4">Navigation Menu (type="default")</h3>
          <p class="mbe4">
            Selection clears when menu closes. Use for navigation and actions.
          </p>
          <VueMenu menu-aria-label="User navigation">
            User Menu
            <template #menu>
              <VueMenuItem value="profile">Profile</VueMenuItem>
              <VueMenuItem value="settings">Settings</VueMenuItem>
              <VueMenuSeparator />
              <VueMenuItem value="logout">Logout</VueMenuItem>
            </template>
          </VueMenu>
        </div>

        <div>
          <h3 class="mbe4">Selection Menu (type="single-select")</h3>
          <p class="mbe4">
            Selection persists when menu closes. Use for filters, sorting, etc.
          </p>
          <VueMenu
            menu-type="single-select"
            selected-value="date"
            menu-aria-label="Sort options"
          >
            Sort by
            <template #menu>
              <VueMenuItem value="date">Date</VueMenuItem>
              <VueMenuItem value="name">Name</VueMenuItem>
              <VueMenuItem value="size">Size</VueMenuItem>
            </template>
          </VueMenu>
        </div>
      </div>
    </div>

    <div class="mbe4">
      <h2>Additional Gutter</h2>
      <p class="mbe4">
        The <code>additional-gutter</code> prop adds extra vertical spacing beyond the trigger button's height when positioning the menu. This is useful when the menu button is within a taller container (like a header) and you need the menu to clear that container.
      </p>
    </div>
    <div class="stacked mbe4">
      <div>
        <p class="mbe4">This menu has <code>additional-gutter="20px"</code></p>
        <VueMenu
          menu-variant="chevron"
          additional-gutter="20px"
          menu-aria-label="Menu with additional gutter"
        >
          Menu with Extra Gutter
          <template #menu>
            <VueMenuItem value="option1">Option 1</VueMenuItem>
            <VueMenuItem value="option2">Option 2</VueMenuItem>
            <VueMenuItem value="option3">Option 3</VueMenuItem>
          </template>
        </VueMenu>
      </div>
    </div>

    <div class="mbe4">
      <h2>Responsive Hidden Items (checkHiddenItems)</h2>
      <p class="mbe4">
        The <code>check-hidden-items</code> prop enables the menu to skip items that are hidden via CSS (like responsive media queries). This is useful when you wrap menu items in responsive containers but want keyboard navigation to work correctly.
      </p>
      <p class="mbe4">
        <strong>Performance Note:</strong> Enabling this feature checks computed styles on every keyboard navigation, which has a performance cost. Only enable it if you're using CSS-based hiding. For better performance, prefer conditional rendering (v-if) instead.
      </p>
    </div>
    <div class="stacked mbe4">
      <div v-html="responsiveStyles"></div>
      <div>
        <p class="mbe4">
          <strong>Try resizing your browser:</strong> Desktop items are hidden on mobile (&lt;768px), mobile items are hidden on desktop. Keyboard navigation skips hidden items.
        </p>
        <VueMenu
          check-hidden-items
          menu-aria-label="Responsive menu"
        >
          Responsive Menu
          <template #menu>
            <div class="desktop-only-items">
              <VueMenuItem value="desktop1">
                <Monitor
                  :size="16"
                  class="mie2"
                />Desktop Item 1
              </VueMenuItem>
              <VueMenuItem value="desktop2">
                <Monitor
                  :size="16"
                  class="mie2"
                />Desktop Item 2
              </VueMenuItem>
            </div>
            <div class="mobile-only-items">
              <VueMenuItem value="mobile1">
                <Smartphone
                  :size="16"
                  class="mie2"
                />Mobile Item 1
              </VueMenuItem>
              <VueMenuItem value="mobile2">
                <Smartphone
                  :size="16"
                  class="mie2"
                />Mobile Item 2
              </VueMenuItem>
            </div>
            <VueMenuSeparator />
            <VueMenuItem value="always">Always Visible</VueMenuItem>
          </template>
        </VueMenu>
      </div>
    </div>

    <div class="mbe4">
      <h2>Recommended: Conditional Rendering (Better Performance)</h2>
      <p class="mbe4">
        Instead of using <code>check-hidden-items</code>, you can achieve the same result with better performance by using Vue's conditional rendering (v-if). This removes hidden items from the DOM entirely.
      </p>
    </div>
    <div class="stacked mbe4">
      <div>
        <p class="mbe4">
          <strong>Current viewport:</strong> {{ isMobile ? 'Mobile' : 'Desktop' }}
        </p>
        <VueMenu menu-aria-label="Conditional menu">
          Conditional Menu
          <template #menu>
            <VueMenuItem
              v-if="!isMobile"
              value="desktop1"
            >
              <Monitor
                :size="16"
                class="mie2"
              />Desktop Item 1
            </VueMenuItem>
            <VueMenuItem
              v-if="!isMobile"
              value="desktop2"
            >
              <Monitor
                :size="16"
                class="mie2"
              />Desktop Item 2
            </VueMenuItem>
            <VueMenuItem
              v-if="isMobile"
              value="mobile1"
            >
              <Smartphone
                :size="16"
                class="mie2"
              />Mobile Item 1
            </VueMenuItem>
            <VueMenuItem
              v-if="isMobile"
              value="mobile2"
            >
              <Smartphone
                :size="16"
                class="mie2"
              />Mobile Item 2
            </VueMenuItem>
            <VueMenuSeparator />
            <VueMenuItem value="always">Always Visible</VueMenuItem>
          </template>
        </VueMenu>
      </div>
    </div>

    <div class="mbe4">
      <h2>Dynamic Icon Switching</h2>
      <p class="mbe4">
        The menu button exposes a <code>data-menu-open</code> attribute that changes based on the menu state. You can use this with CSS to dynamically switch icons when the menu opens/closes.
      </p>
    </div>
    <div class="stacked mbe4">
      <div v-html="dynamicIconStyles"></div>
      <div
        class="flex-inline items-center"
        style="gap: 1rem;"
      >
        <VueMenu
          menu-variant="icon"
          ghost
          size="sm"
          class="dynamic-icon-menu"
          aria-label="Toggle menu"
          menu-aria-label="Navigation menu"
        >
          <Menu
            :size="14"
            class="menu-icon"
          />
          <X
            :size="14"
            class="close-icon"
          />
          <template #menu>
            <VueMenuItem value="home">Home</VueMenuItem>
            <VueMenuItem value="about">About</VueMenuItem>
            <VueMenuItem value="contact">Contact</VueMenuItem>
            <VueMenuSeparator />
            <VueMenuItem value="logout">Logout</VueMenuItem>
          </template>
        </VueMenu>
        <p>
          Click the icon to see it change from ☰ to ✕
        </p>
      </div>
    </div>

    <div class="mbe4">
      <h2>CSS Shadow Parts Customization</h2>
    </div>
    <div class="stacked mbe4">
      <div v-html="customMenuStyles"></div>
      <VueMenu
        menu-aria-label="Custom menu"
        class="custom-menu-button"
      >
        Custom Menu
        <template #menu>
          <VueMenuItem value="one">Option 1</VueMenuItem>
          <VueMenuItem value="two">Option 2</VueMenuItem>
          <VueMenuSeparator />
          <VueMenuItem value="three">Option 3</VueMenuItem>
        </template>
      </VueMenu>
    </div>
  </section>
</template>

<script>
import { ref, onMounted, onUnmounted } from "vue";
import VueMenu, {
  VueMenuItem,
  VueMenuSeparator,
} from "agnosticui-core/menu/vue";
import { User, Menu, X, Monitor, Smartphone } from "lucide-vue-next";

export default {
  name: "MenuExamples",
  components: {
    VueMenu,
    VueMenuItem,
    VueMenuSeparator,
    User,
    Menu,
    X,
    Monitor,
    Smartphone,
  },
  setup() {
    const lastEvent = ref(null);
    const lastSelectedValue = ref(null);
    const isMobile = ref(false);

    const checkMobile = () => {
      isMobile.value = window.innerWidth < 768;
    };

    onMounted(() => {
      checkMobile();
      window.addEventListener("resize", checkMobile);
    });

    onUnmounted(() => {
      window.removeEventListener("resize", checkMobile);
    });

    const handleMenuOpen = (detail) => {
      const selectedInfo = lastSelectedValue.value
        ? `, selected: ${lastSelectedValue.value}`
        : "";
      lastEvent.value = `menu-open (open: ${detail.open}${selectedInfo})`;
    };

    const handleMenuClose = (detail) => {
      const selectedInfo = lastSelectedValue.value
        ? `, selected: ${lastSelectedValue.value}`
        : "";
      lastEvent.value = `menu-close (open: ${detail.open}${selectedInfo})`;
    };

    const handleMenuSelect = (detail) => {
      lastSelectedValue.value = detail.value;
      lastEvent.value = `menu-select (value: ${detail.value})`;
    };

    const responsiveStyles = `
      <style>
        /* Hide desktop items on mobile (< 768px) */
        @media (max-width: 767px) {
          .desktop-only-items {
            display: none;
          }
        }
        
        /* Hide mobile items on desktop (>= 768px) */
        @media (min-width: 768px) {
          .mobile-only-items {
            display: none;
          }
        }
      </style>
    `;

    const dynamicIconStyles = `
      <style>
        .dynamic-icon-menu .menu-icon,
        .dynamic-icon-menu .close-icon {
          transition: opacity var(--ag-motion-medium) ease-in-out;
        }
        .dynamic-icon-menu[data-menu-open="false"] .close-icon {
          opacity: 0;
          pointer-events: none;
          position: absolute;
        }
        .dynamic-icon-menu[data-menu-open="true"] .menu-icon {
          opacity: 0;
          pointer-events: none;
          position: absolute;
        }
      </style>
    `;

    const customMenuStyles = `
      <style>
        .custom-menu-button ag-button::part(ag-button) {
          background-color: #4a5568;
          color: white;
          border: 2px solid #2d3748;
          border-radius: 8px;
        }
        .custom-menu-button .label {
          font-weight: bold;
        }
        .custom-menu-button .chevron-icon {
          color: #a0aec0;
        }
        .custom-menu-button ag-menu::part(ag-menu) {
          background-color: #2d3748;
          border: 1px solid #4a5568;
          border-radius: 8px;
        }
        .custom-menu-button ag-menu::part(ag-menu-item) {
          color: #e2e8f0;
        }
        .custom-menu-button ag-menu::part(ag-menu-item):hover {
          background-color: #4a5568;
        }
        .custom-menu-button ag-menu::part(ag-menu-separator) {
          background-color: #4a5568;
        }
        .custom-menu-button ag-menu-item::part(ag-menu-item-button) {
          color: white;
        }
        .custom-menu-button ag-menu-item::part(ag-menu-item-button):focus,
        .custom-menu-button ag-menu-item::part(ag-menu-item-button):hover {
          color: black;
        }
      </style>
    `;

    return {
      lastEvent,
      lastSelectedValue,
      isMobile,
      handleMenuOpen,
      handleMenuClose,
      handleMenuSelect,
      responsiveStyles,
      dynamicIconStyles,
      customMenuStyles,
    };
  },
};
</script>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/menu';

export class MenuLitExamples extends LitElement {
  createRenderRoot() {
    return this;
  }

  static get properties() {
    return {
      lastEvent: { type: String, state: true },
      lastSelectedValue: { type: String, state: true },
      isMobile: { type: Boolean, state: true },
    };
  }

  constructor() {
    super();
    this.lastEvent = null;
    this.lastSelectedValue = null;
    this.isMobile = false;
  }

  connectedCallback() {
    super.connectedCallback();
    this.checkMobile();
    window.addEventListener('resize', this.checkMobile.bind(this));
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    window.removeEventListener('resize', this.checkMobile.bind(this));
  }

  checkMobile() {
    this.isMobile = window.innerWidth < 768;
  }

  handleMenuOpen(e) {
    const selectedInfo = this.lastSelectedValue ? `, selected: ${this.lastSelectedValue}` : '';
    this.lastEvent = `menu-open (open: ${e.detail.open}${selectedInfo})`;
  }

  handleMenuClose(e) {
    const selectedInfo = this.lastSelectedValue ? `, selected: ${this.lastSelectedValue}` : '';
    this.lastEvent = `menu-close (open: ${e.detail.open}${selectedInfo})`;
  }

  handleMenuSelect(e) {
    this.lastSelectedValue = e.detail.value;
    this.lastEvent = `menu-select (value: ${e.detail.value})`;
  }

  render() {
    return html`
      <style>
        /* Responsive styles */
        @media (max-width: 767px) {
          .desktop-only-items {
            display: none;
          }
        }
        @media (min-width: 768px) {
          .mobile-only-items {
            display: none;
          }
        }

        /* Dynamic icon styles */
        .dynamic-icon-menu .menu-icon,
        .dynamic-icon-menu .close-icon {
          transition: opacity var(--ag-motion-medium) ease-in-out;
        }
        .dynamic-icon-menu[data-menu-open="false"] .close-icon {
          opacity: 0;
          pointer-events: none;
          position: absolute;
        }
        .dynamic-icon-menu[data-menu-open="true"] .menu-icon {
          opacity: 0;
          pointer-events: none;
          position: absolute;
        }

        /* Custom menu styles */
        .custom-menu-button ag-button::part(ag-button) {
          background-color: #4a5568;
          color: white;
          border: 2px solid #2d3748;
          border-radius: 8px;
        }
        .custom-menu-button .label {
          font-weight: bold;
        }
        .custom-menu-button .chevron-icon {
          color: #a0aec0;
        }
        .custom-menu-button ag-menu::part(ag-menu) {
          background-color: #2d3748;
          border: 1px solid #4a5568;
          border-radius: 8px;
        }
        .custom-menu-button ag-menu-item::part(ag-menu-item-button) {
          color: white;
        }
        .custom-menu-button ag-menu-item::part(ag-menu-item-button):focus,
        .custom-menu-button ag-menu-item::part(ag-menu-item-button):hover {
          color: black;
        }
      </style>

      <section>
        <div class="mbe4">
          <h2>Basic Menu</h2>
        </div>
        <div class="stacked mbe4">
          <ag-menu-button menu-aria-label="Menu options">
            Menu
            <ag-menu slot="menu">
              <ag-menu-item value="edit">Edit</ag-menu-item>
              <ag-menu-item value="copy">Copy</ag-menu-item>
              <ag-menu-item value="paste">Paste</ag-menu-item>
              <ag-menu-separator></ag-menu-separator>
              <ag-menu-item value="delete">Delete</ag-menu-item>
            </ag-menu>
          </ag-menu-button>
        </div>

        <div class="mbe4">
          <h2>Menu Alignment</h2>
          <p class="mbe4">
            The <code>menu-align</code> prop controls horizontal alignment. Use <code>menu-align="right"</code> when the menu button is near the right edge of the viewport.
          </p>
        </div>
        <div class="stacked mbe4">
          <div class="flex items-center" style="gap: 1rem; justify-content: space-between;">
            <ag-menu-button menu-align="left" menu-aria-label="Left-aligned menu">
              Left Aligned
              <ag-menu slot="menu">
                <ag-menu-item value="option1">Option 1</ag-menu-item>
                <ag-menu-item value="option2">Option 2</ag-menu-item>
                <ag-menu-item value="option3">Option 3</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
            <ag-menu-button menu-align="right" menu-aria-label="Right-aligned menu">
              Right Aligned
              <ag-menu slot="menu">
                <ag-menu-item value="option1">Option 1</ag-menu-item>
                <ag-menu-item value="option2">Option 2</ag-menu-item>
                <ag-menu-item value="option3">Option 3</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
          </div>
        </div>

        <div class="mbe4">
          <h2>Menu with Links</h2>
        </div>
        <div class="stacked mbe4">
          <ag-menu-button menu-aria-label="Navigation menu">
            Navigation
            <ag-menu slot="menu">
              <ag-menu-item value="home" href="#home">Home</ag-menu-item>
              <ag-menu-item value="about" href="#about">About</ag-menu-item>
              <ag-menu-item value="contact" href="#contact">Contact</ag-menu-item>
              <ag-menu-separator></ag-menu-separator>
              <ag-menu-item value="external" href="https://example.com" target="_blank">
                External Link
              </ag-menu-item>
            </ag-menu>
          </ag-menu-button>
        </div>

        <div class="mbe4">
          <h2>Disabled Button</h2>
        </div>
        <div class="stacked mbe4">
          <ag-menu-button disabled menu-aria-label="Disabled menu">
            Disabled Menu
            <ag-menu slot="menu">
              <ag-menu-item value="item1">Item 1</ag-menu-item>
              <ag-menu-item value="item2">Item 2</ag-menu-item>
            </ag-menu>
          </ag-menu-button>
        </div>

        <div class="mbe4">
          <h2>Menu with Disabled Items</h2>
        </div>
        <div class="stacked mbe4">
          <ag-menu-button menu-aria-label="Menu with disabled items">
            Mixed States
            <ag-menu slot="menu">
              <ag-menu-item value="enabled1">Enabled Item</ag-menu-item>
              <ag-menu-item value="disabled1" disabled>Disabled Item</ag-menu-item>
              <ag-menu-item value="enabled2">Another Enabled</ag-menu-item>
              <ag-menu-separator></ag-menu-separator>
              <ag-menu-item value="disabled2" disabled>Another Disabled</ag-menu-item>
            </ag-menu>
          </ag-menu-button>
        </div>

        <div class="mbe4">
          <h2>Complex Menu (File Menu)</h2>
        </div>
        <div class="stacked mbe4">
          <ag-menu-button menu-aria-label="File menu">
            File
            <ag-menu slot="menu">
              <ag-menu-item value="new">New</ag-menu-item>
              <ag-menu-item value="open">Open...</ag-menu-item>
              <ag-menu-item value="recent">Open Recent</ag-menu-item>
              <ag-menu-separator></ag-menu-separator>
              <ag-menu-item value="save">Save</ag-menu-item>
              <ag-menu-item value="save-as">Save As...</ag-menu-item>
              <ag-menu-separator></ag-menu-separator>
              <ag-menu-item value="export">Export</ag-menu-item>
              <ag-menu-item value="import">Import</ag-menu-item>
              <ag-menu-separator></ag-menu-separator>
              <ag-menu-item value="print">Print</ag-menu-item>
              <ag-menu-separator></ag-menu-separator>
              <ag-menu-item value="close">Close</ag-menu-item>
            </ag-menu>
          </ag-menu-button>
        </div>

        <div class="mbe4">
          <h2>Menu with Sections</h2>
        </div>
        <div class="stacked mbe4">
          <ag-menu-button menu-aria-label="User menu">
            <div class="flex-inline items-center">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" class="mie2">
                <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
                <circle cx="12" cy="7" r="4"/>
              </svg>
              User Menu
            </div>
            <ag-menu slot="menu">
              <ag-menu-item value="profile">Profile</ag-menu-item>
              <ag-menu-item value="settings">Settings</ag-menu-item>
              <ag-menu-separator></ag-menu-separator>
              <ag-menu-item value="help">Help</ag-menu-item>
              <ag-menu-item value="feedback">Send Feedback</ag-menu-item>
              <ag-menu-separator></ag-menu-separator>
              <ag-menu-item value="logout">Logout</ag-menu-item>
            </ag-menu>
          </ag-menu-button>
        </div>

        <div class="mbe4">
          <h2>Menu with Icon Button</h2>
          <p class="mbe4">
            The following examples show using an icon as the trigger. The <code>menu-variant="icon"</code> removes the chevron and provides a minimal button container for the icon. You can also use an icon with the default chevron trigger as we see on the right.
          </p>
        </div>
        <div class="mbe4">
          <h3>Large</h3>
          <div class="flex-inline items-center" style="gap: 1rem;">
            <!-- Icon only -->
            <ag-menu-button menu-variant="icon" ghost menu-aria-label="More options menu">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="24" height="24">
                <line x1="3" y1="12" x2="21" y2="12"/>
                <line x1="3" y1="6" x2="21" y2="6"/>
                <line x1="3" y1="18" x2="21" y2="18"/>
              </svg>
              <ag-menu slot="menu">
                <ag-menu-item value="settings">Settings</ag-menu-item>
                <ag-menu-item value="profile">Profile</ag-menu-item>
                <ag-menu-separator></ag-menu-separator>
                <ag-menu-item value="logout">Logout</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
            <!-- Icon with chevron -->
            <ag-menu-button menu-aria-label="More options menu">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="24" height="24">
                <line x1="3" y1="12" x2="21" y2="12"/>
                <line x1="3" y1="6" x2="21" y2="6"/>
                <line x1="3" y1="18" x2="21" y2="18"/>
              </svg>
              <ag-menu slot="menu">
                <ag-menu-item value="settings">Settings</ag-menu-item>
                <ag-menu-item value="profile">Profile</ag-menu-item>
                <ag-menu-separator></ag-menu-separator>
                <ag-menu-item value="logout">Logout</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
          </div>
        </div>
        <div class="mbe4">
          <h3>Medium</h3>
          <div class="flex-inline items-center" style="gap: 1rem;">
            <!-- Icon only -->
            <ag-menu-button menu-variant="icon" ghost size="sm" menu-aria-label="More options menu">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18">
                <line x1="3" y1="12" x2="21" y2="12"/>
                <line x1="3" y1="6" x2="21" y2="6"/>
                <line x1="3" y1="18" x2="21" y2="18"/>
              </svg>
              <ag-menu slot="menu">
                <ag-menu-item value="settings">Settings</ag-menu-item>
                <ag-menu-item value="profile">Profile</ag-menu-item>
                <ag-menu-separator></ag-menu-separator>
                <ag-menu-item value="logout">Logout</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
            <!-- Icon with chevron -->
            <ag-menu-button size="sm" menu-aria-label="More options menu">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18">
                <line x1="3" y1="12" x2="21" y2="12"/>
                <line x1="3" y1="6" x2="21" y2="6"/>
                <line x1="3" y1="18" x2="21" y2="18"/>
              </svg>
              <ag-menu slot="menu">
                <ag-menu-item value="settings">Settings</ag-menu-item>
                <ag-menu-item value="profile">Profile</ag-menu-item>
                <ag-menu-separator></ag-menu-separator>
                <ag-menu-item value="logout">Logout</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
          </div>
        </div>
        <div class="mbe4">
          <h3>Small</h3>
          <div class="flex-inline items-center" style="gap: 1rem;">
            <!-- Icon only -->
            <ag-menu-button menu-variant="icon" ghost size="x-sm" menu-aria-label="More options menu">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14">
                <line x1="3" y1="12" x2="21" y2="12"/>
                <line x1="3" y1="6" x2="21" y2="6"/>
                <line x1="3" y1="18" x2="21" y2="18"/>
              </svg>
              <ag-menu slot="menu">
                <ag-menu-item value="settings">Settings</ag-menu-item>
                <ag-menu-item value="profile">Profile</ag-menu-item>
                <ag-menu-separator></ag-menu-separator>
                <ag-menu-item value="logout">Logout</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
            <!-- Icon with chevron -->
            <ag-menu-button size="x-sm" menu-aria-label="More options menu">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14">
                <line x1="3" y1="12" x2="21" y2="12"/>
                <line x1="3" y1="6" x2="21" y2="6"/>
                <line x1="3" y1="18" x2="21" y2="18"/>
              </svg>
              <ag-menu slot="menu">
                <ag-menu-item value="settings">Settings</ag-menu-item>
                <ag-menu-item value="profile">Profile</ag-menu-item>
                <ag-menu-separator></ag-menu-separator>
                <ag-menu-item value="logout">Logout</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
          </div>
        </div>

        <div class="mbe4">
          <h2>Monochrome Variant</h2>
        </div>
        <div class="stacked mbe4">
          <ag-menu-button menu-aria-label="Monochrome menu" button-variant="monochrome">
            Monochrome Menu
            <ag-menu slot="menu">
              <ag-menu-item value="option1" variant="monochrome">Option 1</ag-menu-item>
              <ag-menu-item value="option2" variant="monochrome" selected>Option 2 (Selected)</ag-menu-item>
              <ag-menu-item value="option3" variant="monochrome">Option 3</ag-menu-item>
            </ag-menu>
          </ag-menu-button>
        </div>

        <div class="mbe4">
          <h2>Event Handling</h2>
        </div>
        <div class="stacked mbe4">
          <div class="flex-inline items-center" style="gap: 10px;">
            <ag-menu-button
              menu-aria-label="Event testing menu"
              @menu-open=${this.handleMenuOpen}
              @menu-close=${this.handleMenuClose}
              @menu-select=${this.handleMenuSelect}
            >
              Event Test
              <ag-menu slot="menu">
                <ag-menu-item value="option1">Option 1</ag-menu-item>
                <ag-menu-item value="option2">Option 2</ag-menu-item>
                <ag-menu-separator></ag-menu-separator>
                <ag-menu-item value="option3">Option 3</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
            ${this.lastEvent ? html`<p>Last event: <strong>${this.lastEvent}</strong></p>` : ''}
          </div>
        </div>

        <div class="mbe4">
          <h2>Menu Types: Navigation vs Selection</h2>
          <p class="mbe4">
            The <code>type</code> prop controls selection behavior. Use <code>type="default"</code> (the default) for navigation menus where selection is transient, and <code>type="single-select"</code> for menus where selection should persist (like filters or sorting).
          </p>
        </div>
        <div class="stacked mbe4">
          <div class="flex items-start" style="gap: 2rem; flex-wrap: wrap;">
            <div>
              <h3 class="mbe4">Navigation Menu (type="default")</h3>
              <p class="mbe4">
                Selection clears when menu closes. Use for navigation and actions.
              </p>
              <ag-menu-button menu-aria-label="User navigation">
                User Menu
                <ag-menu slot="menu">
                  <ag-menu-item value="profile">Profile</ag-menu-item>
                  <ag-menu-item value="settings">Settings</ag-menu-item>
                  <ag-menu-separator></ag-menu-separator>
                  <ag-menu-item value="logout">Logout</ag-menu-item>
                </ag-menu>
              </ag-menu-button>
            </div>

            <div>
              <h3 class="mbe4">Selection Menu (type="single-select")</h3>
              <p class="mbe4">
                Selection persists when menu closes. Use for filters, sorting, etc.
              </p>
              <ag-menu-button menu-type="single-select" selected-value="date" menu-aria-label="Sort options">
                Sort by
                <ag-menu slot="menu">
                  <ag-menu-item value="date">Date</ag-menu-item>
                  <ag-menu-item value="name">Name</ag-menu-item>
                  <ag-menu-item value="size">Size</ag-menu-item>
                </ag-menu>
              </ag-menu-button>
            </div>
          </div>
        </div>

        <div class="mbe4">
          <h2>Additional Gutter</h2>
          <p class="mbe4">
            The <code>additional-gutter</code> prop adds extra vertical spacing beyond the trigger button's height when positioning the menu. This is useful when the menu button is within a taller container (like a header) and you need the menu to clear that container.
          </p>
        </div>
        <div class="stacked mbe4">
          <div>
            <p class="mbe4">This menu has <code>additional-gutter="20px"</code></p>
            <ag-menu-button menu-variant="chevron" additional-gutter="20px" menu-aria-label="Menu with additional gutter">
              Menu with Extra Gutter
              <ag-menu slot="menu">
                <ag-menu-item value="option1">Option 1</ag-menu-item>
                <ag-menu-item value="option2">Option 2</ag-menu-item>
                <ag-menu-item value="option3">Option 3</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
          </div>
        </div>

        <div class="mbe4">
          <h2>Responsive Hidden Items (checkHiddenItems)</h2>
          <p class="mbe4">
            The <code>check-hidden-items</code> prop enables the menu to skip items that are hidden via CSS (like responsive media queries). This is useful when you wrap menu items in responsive containers but want keyboard navigation to work correctly.
          </p>
          <p class="mbe4">
            <strong>Performance Note:</strong> Enabling this feature checks computed styles on every keyboard navigation, which has a performance cost. Only enable it if you're using CSS-based hiding. For better performance, prefer conditional rendering instead.
          </p>
        </div>
        <div class="stacked mbe4">
          <div>
            <p class="mbe4">
              <strong>Try resizing your browser:</strong> Desktop items are hidden on mobile (&lt;768px), mobile items are hidden on desktop. Keyboard navigation skips hidden items.
            </p>
            <ag-menu-button check-hidden-items menu-aria-label="Responsive menu">
              Responsive Menu
              <ag-menu slot="menu">
                <div class="desktop-only-items">
                  <ag-menu-item value="desktop1">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" class="mie2" style="display: inline-block; vertical-align: middle;">
                      <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
                      <line x1="8" y1="21" x2="16" y2="21"/>
                      <line x1="12" y1="17" x2="12" y2="21"/>
                    </svg>Desktop Item 1
                  </ag-menu-item>
                  <ag-menu-item value="desktop2">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" class="mie2" style="display: inline-block; vertical-align: middle;">
                      <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
                      <line x1="8" y1="21" x2="16" y2="21"/>
                      <line x1="12" y1="17" x2="12" y2="21"/>
                    </svg>Desktop Item 2
                  </ag-menu-item>
                </div>
                <div class="mobile-only-items">
                  <ag-menu-item value="mobile1">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" class="mie2" style="display: inline-block; vertical-align: middle;">
                      <rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
                      <line x1="12" y1="18" x2="12.01" y2="18"/>
                    </svg>Mobile Item 1
                  </ag-menu-item>
                  <ag-menu-item value="mobile2">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" class="mie2" style="display: inline-block; vertical-align: middle;">
                      <rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
                      <line x1="12" y1="18" x2="12.01" y2="18"/>
                    </svg>Mobile Item 2
                  </ag-menu-item>
                </div>
                <ag-menu-separator></ag-menu-separator>
                <ag-menu-item value="always">Always Visible</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
          </div>
        </div>

        <div class="mbe4">
          <h2>Recommended: Conditional Rendering (Better Performance)</h2>
          <p class="mbe4">
            Instead of using <code>check-hidden-items</code>, you can achieve the same result with better performance by using conditional rendering. This removes hidden items from the DOM entirely.
          </p>
        </div>
        <div class="stacked mbe4">
          <div>
            <p class="mbe4">
              <strong>Current viewport:</strong> ${this.isMobile ? 'Mobile' : 'Desktop'}
            </p>
            <ag-menu-button menu-aria-label="Conditional menu">
              Conditional Menu
              <ag-menu slot="menu">
                ${!this.isMobile ? html`
                  <ag-menu-item value="desktop1">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" class="mie2" style="display: inline-block; vertical-align: middle;">
                      <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
                      <line x1="8" y1="21" x2="16" y2="21"/>
                      <line x1="12" y1="17" x2="12" y2="21"/>
                    </svg>Desktop Item 1
                  </ag-menu-item>
                  <ag-menu-item value="desktop2">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" class="mie2" style="display: inline-block; vertical-align: middle;">
                      <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
                      <line x1="8" y1="21" x2="16" y2="21"/>
                      <line x1="12" y1="17" x2="12" y2="21"/>
                    </svg>Desktop Item 2
                  </ag-menu-item>
                ` : ''}
                ${this.isMobile ? html`
                  <ag-menu-item value="mobile1">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" class="mie2" style="display: inline-block; vertical-align: middle;">
                      <rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
                      <line x1="12" y1="18" x2="12.01" y2="18"/>
                    </svg>Mobile Item 1
                  </ag-menu-item>
                  <ag-menu-item value="mobile2">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" class="mie2" style="display: inline-block; vertical-align: middle;">
                      <rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
                      <line x1="12" y1="18" x2="12.01" y2="18"/>
                    </svg>Mobile Item 2
                  </ag-menu-item>
                ` : ''}
                <ag-menu-separator></ag-menu-separator>
                <ag-menu-item value="always">Always Visible</ag-menu-item>
              </ag-menu>
            </ag-menu-button>
          </div>
        </div>
      </section>
    `;
  }
}

// Register the custom element
customElements.define('menu-lit-examples', MenuLitExamples);

Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.

View React Code
import { useState, useEffect } from "react";
import {
  ReactMenuButton,
  ReactMenu,
  ReactMenuItem,
  ReactMenuSeparator,
} from "agnosticui-core/menu/react";

export default function MenuReactExamples() {
  const [lastEvent, setLastEvent] = useState(null);
  const [lastSelectedValue, setLastSelectedValue] = useState(null);
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const checkMobile = () => {
      setIsMobile(window.innerWidth < 768);
    };

    checkMobile();
    window.addEventListener("resize", checkMobile);
    return () => window.removeEventListener("resize", checkMobile);
  }, []);

  const handleMenuOpen = (e) => {
    const selectedInfo = lastSelectedValue
      ? `, selected: ${lastSelectedValue}`
      : "";
    setLastEvent(`menu-open (open: ${e.detail.open}${selectedInfo})`);
  };

  const handleMenuClose = (e) => {
    const selectedInfo = lastSelectedValue
      ? `, selected: ${lastSelectedValue}`
      : "";
    setLastEvent(`menu-close (open: ${e.detail.open}${selectedInfo})`);
  };

  const handleMenuSelect = (e) => {
    setLastSelectedValue(e.detail.value);
    setLastEvent(`menu-select (value: ${e.detail.value})`);
  };

  return (
    <>
      <style>{`
        /* Responsive styles */
        @media (max-width: 767px) {
          .desktop-only-items {
            display: none;
          }
        }
        @media (min-width: 768px) {
          .mobile-only-items {
            display: none;
          }
        }

        /* Dynamic icon styles */
        .dynamic-icon-menu .menu-icon,
        .dynamic-icon-menu .close-icon {
          transition: opacity var(--ag-motion-medium) ease-in-out;
        }
        .dynamic-icon-menu[data-menu-open="false"] .close-icon {
          opacity: 0;
          pointer-events: none;
          position: absolute;
        }
        .dynamic-icon-menu[data-menu-open="true"] .menu-icon {
          opacity: 0;
          pointer-events: none;
          position: absolute;
        }

        /* Custom menu styles */
        .custom-menu-button ag-button::part(ag-button) {
          background-color: #4a5568;
          color: white;
          border: 2px solid #2d3748;
          border-radius: 8px;
        }
        .custom-menu-button .label {
          font-weight: bold;
        }
        .custom-menu-button .chevron-icon {
          color: #a0aec0;
        }
        .custom-menu-button ag-menu::part(ag-menu) {
          background-color: #2d3748;
          border: 1px solid #4a5568;
          border-radius: 8px;
        }
        .custom-menu-button ag-menu-item::part(ag-menu-item-button) {
          color: white;
        }
        .custom-menu-button ag-menu-item::part(ag-menu-item-button):focus,
        .custom-menu-button ag-menu-item::part(ag-menu-item-button):hover {
          color: black;
        }
      `}</style>

      <section>
        <div className="mbe4">
          <h2>Basic Menu</h2>
        </div>
        <div className="stacked mbe4">
          <ReactMenuButton menuAriaLabel="Menu options">
            Menu
            <ReactMenu slot="menu">
              <ReactMenuItem value="edit">Edit</ReactMenuItem>
              <ReactMenuItem value="copy">Copy</ReactMenuItem>
              <ReactMenuItem value="paste">Paste</ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem value="delete">Delete</ReactMenuItem>
            </ReactMenu>
          </ReactMenuButton>
        </div>

        <div className="mbe4">
          <h2>Menu Alignment</h2>
          <p className="mbe4">
            The <code>menu-align</code> prop controls horizontal alignment. Use{" "}
            <code>menu-align="right"</code> when the menu button is near the
            right edge of the viewport.
          </p>
        </div>
        <div className="stacked mbe4">
          <div
            className="flex items-center"
            style={{ gap: "1rem", justifyContent: "space-between" }}
          >
            <ReactMenuButton menuAlign="left" menuAriaLabel="Left-aligned menu">
              Left Aligned
              <ReactMenu slot="menu">
                <ReactMenuItem value="option1">Option 1</ReactMenuItem>
                <ReactMenuItem value="option2">Option 2</ReactMenuItem>
                <ReactMenuItem value="option3">Option 3</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
            <ReactMenuButton
              menuAlign="right"
              menuAriaLabel="Right-aligned menu"
            >
              Right Aligned
              <ReactMenu slot="menu">
                <ReactMenuItem value="option1">Option 1</ReactMenuItem>
                <ReactMenuItem value="option2">Option 2</ReactMenuItem>
                <ReactMenuItem value="option3">Option 3</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
          </div>
        </div>

        <div className="mbe4">
          <h2>Menu with Links</h2>
        </div>
        <div className="stacked mbe4">
          <ReactMenuButton menuAriaLabel="Navigation menu">
            Navigation
            <ReactMenu slot="menu">
              <ReactMenuItem value="home" href="#home">
                Home
              </ReactMenuItem>
              <ReactMenuItem value="about" href="#about">
                About
              </ReactMenuItem>
              <ReactMenuItem value="contact" href="#contact">
                Contact
              </ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem
                value="external"
                href="https://example.com"
                target="_blank"
              >
                External Link
              </ReactMenuItem>
            </ReactMenu>
          </ReactMenuButton>
        </div>

        <div className="mbe4">
          <h2>Disabled Button</h2>
        </div>
        <div className="stacked mbe4">
          <ReactMenuButton disabled menuAriaLabel="Disabled menu">
            Disabled Menu
            <ReactMenu slot="menu">
              <ReactMenuItem value="item1">Item 1</ReactMenuItem>
              <ReactMenuItem value="item2">Item 2</ReactMenuItem>
            </ReactMenu>
          </ReactMenuButton>
        </div>

        <div className="mbe4">
          <h2>Menu with Disabled Items</h2>
        </div>
        <div className="stacked mbe4">
          <ReactMenuButton menuAriaLabel="Menu with disabled items">
            Mixed States
            <ReactMenu slot="menu">
              <ReactMenuItem value="enabled1">Enabled Item</ReactMenuItem>
              <ReactMenuItem value="disabled1" disabled>
                Disabled Item
              </ReactMenuItem>
              <ReactMenuItem value="enabled2">Another Enabled</ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem value="disabled2" disabled>
                Another Disabled
              </ReactMenuItem>
            </ReactMenu>
          </ReactMenuButton>
        </div>

        <div className="mbe4">
          <h2>Complex Menu (File Menu)</h2>
        </div>
        <div className="stacked mbe4">
          <ReactMenuButton menuAriaLabel="File menu">
            File
            <ReactMenu slot="menu">
              <ReactMenuItem value="new">New</ReactMenuItem>
              <ReactMenuItem value="open">Open...</ReactMenuItem>
              <ReactMenuItem value="recent">Open Recent</ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem value="save">Save</ReactMenuItem>
              <ReactMenuItem value="save-as">Save As...</ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem value="export">Export</ReactMenuItem>
              <ReactMenuItem value="import">Import</ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem value="print">Print</ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem value="close">Close</ReactMenuItem>
            </ReactMenu>
          </ReactMenuButton>
        </div>

        <div className="mbe4">
          <h2>Menu with Sections</h2>
        </div>
        <div className="stacked mbe4">
          <ReactMenuButton menuAriaLabel="User menu">
            <div className="flex-inline items-center">
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                width="16"
                height="16"
                className="mie2"
              >
                <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
                <circle cx="12" cy="7" r="4" />
              </svg>
              User Menu
            </div>
            <ReactMenu slot="menu">
              <ReactMenuItem value="profile">Profile</ReactMenuItem>
              <ReactMenuItem value="settings">Settings</ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem value="help">Help</ReactMenuItem>
              <ReactMenuItem value="feedback">Send Feedback</ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem value="logout">Logout</ReactMenuItem>
            </ReactMenu>
          </ReactMenuButton>
        </div>

        <div className="mbe4">
          <h2>Menu with Icon Button</h2>
          <p className="mbe4">
            The following examples show using an icon as the trigger. The{" "}
            <code>menu-variant="icon"</code> removes the chevron and provides a
            minimal button container for the icon. You can also use an icon with
            the default chevron trigger as we see on the right.
          </p>
        </div>
        <div className="mbe4">
          <h3>Large</h3>
          <div className="flex-inline items-center" style={{ gap: "1rem" }}>
            {/* Icon only */}
            <ReactMenuButton
              menuVariant="icon"
              ghost
              menuAriaLabel="More options menu"
            >
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                width="24"
                height="24"
              >
                <line x1="3" y1="12" x2="21" y2="12" />
                <line x1="3" y1="6" x2="21" y2="6" />
                <line x1="3" y1="18" x2="21" y2="18" />
              </svg>
              <ReactMenu slot="menu">
                <ReactMenuItem value="settings">Settings</ReactMenuItem>
                <ReactMenuItem value="profile">Profile</ReactMenuItem>
                <ReactMenuSeparator />
                <ReactMenuItem value="logout">Logout</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
            {/* Icon with chevron */}
            <ReactMenuButton menuAriaLabel="More options menu">
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                width="24"
                height="24"
              >
                <line x1="3" y1="12" x2="21" y2="12" />
                <line x1="3" y1="6" x2="21" y2="6" />
                <line x1="3" y1="18" x2="21" y2="18" />
              </svg>
              <ReactMenu slot="menu">
                <ReactMenuItem value="settings">Settings</ReactMenuItem>
                <ReactMenuItem value="profile">Profile</ReactMenuItem>
                <ReactMenuSeparator />
                <ReactMenuItem value="logout">Logout</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
          </div>
        </div>
        <div className="mbe4">
          <h3>Medium</h3>
          <div className="flex-inline items-center" style={{ gap: "1rem" }}>
            {/* Icon only */}
            <ReactMenuButton
              menuVariant="icon"
              ghost
              size="sm"
              menuAriaLabel="More options menu"
            >
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                width="18"
                height="18"
              >
                <line x1="3" y1="12" x2="21" y2="12" />
                <line x1="3" y1="6" x2="21" y2="6" />
                <line x1="3" y1="18" x2="21" y2="18" />
              </svg>
              <ReactMenu slot="menu">
                <ReactMenuItem value="settings">Settings</ReactMenuItem>
                <ReactMenuItem value="profile">Profile</ReactMenuItem>
                <ReactMenuSeparator />
                <ReactMenuItem value="logout">Logout</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
            {/* Icon with chevron */}
            <ReactMenuButton size="sm" menuAriaLabel="More options menu">
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                width="18"
                height="18"
              >
                <line x1="3" y1="12" x2="21" y2="12" />
                <line x1="3" y1="6" x2="21" y2="6" />
                <line x1="3" y1="18" x2="21" y2="18" />
              </svg>
              <ReactMenu slot="menu">
                <ReactMenuItem value="settings">Settings</ReactMenuItem>
                <ReactMenuItem value="profile">Profile</ReactMenuItem>
                <ReactMenuSeparator />
                <ReactMenuItem value="logout">Logout</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
          </div>
        </div>
        <div className="mbe4">
          <h3>Small</h3>
          <div className="flex-inline items-center" style={{ gap: "1rem" }}>
            {/* Icon only */}
            <ReactMenuButton
              menuVariant="icon"
              ghost
              size="x-sm"
              menuAriaLabel="More options menu"
            >
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                width="14"
                height="14"
              >
                <line x1="3" y1="12" x2="21" y2="12" />
                <line x1="3" y1="6" x2="21" y2="6" />
                <line x1="3" y1="18" x2="21" y2="18" />
              </svg>
              <ReactMenu slot="menu">
                <ReactMenuItem value="settings">Settings</ReactMenuItem>
                <ReactMenuItem value="profile">Profile</ReactMenuItem>
                <ReactMenuSeparator />
                <ReactMenuItem value="logout">Logout</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
            {/* Icon with chevron */}
            <ReactMenuButton size="x-sm" menuAriaLabel="More options menu">
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                width="14"
                height="14"
              >
                <line x1="3" y1="12" x2="21" y2="12" />
                <line x1="3" y1="6" x2="21" y2="6" />
                <line x1="3" y1="18" x2="21" y2="18" />
              </svg>
              <ReactMenu slot="menu">
                <ReactMenuItem value="settings">Settings</ReactMenuItem>
                <ReactMenuItem value="profile">Profile</ReactMenuItem>
                <ReactMenuSeparator />
                <ReactMenuItem value="logout">Logout</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
          </div>
        </div>

        <div className="mbe4">
          <h2>Monochrome Variant</h2>
        </div>
        <div className="stacked mbe4">
          <ReactMenuButton
            menuAriaLabel="Monochrome menu"
            buttonVariant="monochrome"
          >
            Monochrome Menu
            <ReactMenu slot="menu">
              <ReactMenuItem value="option1" variant="monochrome">
                Option 1
              </ReactMenuItem>
              <ReactMenuItem value="option2" variant="monochrome" selected>
                Option 2 (Selected)
              </ReactMenuItem>
              <ReactMenuItem value="option3" variant="monochrome">
                Option 3
              </ReactMenuItem>
            </ReactMenu>
          </ReactMenuButton>
        </div>

        <div className="mbe4">
          <h2>Event Handling</h2>
        </div>
        <div className="stacked mbe4">
          <div className="flex-inline items-center" style={{ gap: "10px" }}>
            <ReactMenuButton
              menuAriaLabel="Event testing menu"
              onMenuOpen={handleMenuOpen}
              onMenuClose={handleMenuClose}
              onMenuSelect={handleMenuSelect}
            >
              Event Test
              <ReactMenu slot="menu">
                <ReactMenuItem value="option1">Option 1</ReactMenuItem>
                <ReactMenuItem value="option2">Option 2</ReactMenuItem>
                <ReactMenuSeparator />
                <ReactMenuItem value="option3">Option 3</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
            {lastEvent && (
              <p>
                Last event: <strong>{lastEvent}</strong>
              </p>
            )}
          </div>
        </div>

        <div className="mbe4">
          <h2>Menu Types: Navigation vs Selection</h2>
          <p className="mbe4">
            The <code>type</code> prop controls selection behavior. Use{" "}
            <code>type="default"</code> (the default) for navigation menus where
            selection is transient, and <code>type="single-select"</code> for
            menus where selection should persist (like filters or sorting).
          </p>
        </div>
        <div className="stacked mbe4">
          <div
            className="flex items-start"
            style={{ gap: "2rem", flexWrap: "wrap" }}
          >
            <div>
              <h3 className="mbe4">Navigation Menu (type="default")</h3>
              <p className="mbe4">
                Selection clears when menu closes. Use for navigation and
                actions.
              </p>
              <ReactMenuButton menuAriaLabel="User navigation">
                User Menu
                <ReactMenu slot="menu">
                  <ReactMenuItem value="profile">Profile</ReactMenuItem>
                  <ReactMenuItem value="settings">Settings</ReactMenuItem>
                  <ReactMenuSeparator />
                  <ReactMenuItem value="logout">Logout</ReactMenuItem>
                </ReactMenu>
              </ReactMenuButton>
            </div>

            <div>
              <h3 className="mbe4">Selection Menu (type="single-select")</h3>
              <p className="mbe4">
                Selection persists when menu closes. Use for filters, sorting,
                etc.
              </p>
              <ReactMenuButton
                menuType="single-select"
                selectedValue="date"
                menuAriaLabel="Sort options"
              >
                Sort by
                <ReactMenu slot="menu">
                  <ReactMenuItem value="date">Date</ReactMenuItem>
                  <ReactMenuItem value="name">Name</ReactMenuItem>
                  <ReactMenuItem value="size">Size</ReactMenuItem>
                </ReactMenu>
              </ReactMenuButton>
            </div>
          </div>
        </div>

        <div className="mbe4">
          <h2>Additional Gutter</h2>
          <p className="mbe4">
            The <code>additional-gutter</code> prop adds extra vertical spacing
            beyond the trigger button's height when positioning the menu. This
            is useful when the menu button is within a taller container (like a
            header) and you need the menu to clear that container.
          </p>
        </div>
        <div className="stacked mbe4">
          <div>
            <p className="mbe4">
              This menu has <code>additional-gutter="20px"</code>
            </p>
            <ReactMenuButton
              menuVariant="chevron"
              additionalGutter="20px"
              menuAriaLabel="Menu with additional gutter"
            >
              Menu with Extra Gutter
              <ReactMenu slot="menu">
                <ReactMenuItem value="option1">Option 1</ReactMenuItem>
                <ReactMenuItem value="option2">Option 2</ReactMenuItem>
                <ReactMenuItem value="option3">Option 3</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
          </div>
        </div>

        <div className="mbe4">
          <h2>Responsive Hidden Items (checkHiddenItems)</h2>
          <p className="mbe4">
            The <code>check-hidden-items</code> prop enables the menu to skip
            items that are hidden via CSS (like responsive media queries). This
            is useful when you wrap menu items in responsive containers but want
            keyboard navigation to work correctly.
          </p>
          <p className="mbe4">
            <strong>Performance Note:</strong> Enabling this feature checks
            computed styles on every keyboard navigation, which has a
            performance cost. Only enable it if you're using CSS-based hiding.
            For better performance, prefer conditional rendering instead.
          </p>
        </div>
        <div className="stacked mbe4">
          <div>
            <p className="mbe4">
              <strong>Try resizing your browser:</strong> Desktop items are
              hidden on mobile (&lt;768px), mobile items are hidden on desktop.
              Keyboard navigation skips hidden items.
            </p>
            <ReactMenuButton checkHiddenItems menuAriaLabel="Responsive menu">
              Responsive Menu
              <ReactMenu slot="menu">
                <div className="desktop-only-items">
                  <ReactMenuItem value="desktop1">
                    <svg
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      width="16"
                      height="16"
                      className="mie2"
                      style={{
                        display: "inline-block",
                        verticalAlign: "middle",
                      }}
                    >
                      <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
                      <line x1="8" y1="21" x2="16" y2="21" />
                      <line x1="12" y1="17" x2="12" y2="21" />
                    </svg>
                    Desktop Item 1
                  </ReactMenuItem>
                  <ReactMenuItem value="desktop2">
                    <svg
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      width="16"
                      height="16"
                      className="mie2"
                      style={{
                        display: "inline-block",
                        verticalAlign: "middle",
                      }}
                    >
                      <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
                      <line x1="8" y1="21" x2="16" y2="21" />
                      <line x1="12" y1="17" x2="12" y2="21" />
                    </svg>
                    Desktop Item 2
                  </ReactMenuItem>
                </div>
                <div className="mobile-only-items">
                  <ReactMenuItem value="mobile1">
                    <svg
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      width="16"
                      height="16"
                      className="mie2"
                      style={{
                        display: "inline-block",
                        verticalAlign: "middle",
                      }}
                    >
                      <rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
                      <line x1="12" y1="18" x2="12.01" y2="18" />
                    </svg>
                    Mobile Item 1
                  </ReactMenuItem>
                  <ReactMenuItem value="mobile2">
                    <svg
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      width="16"
                      height="16"
                      className="mie2"
                      style={{
                        display: "inline-block",
                        verticalAlign: "middle",
                      }}
                    >
                      <rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
                      <line x1="12" y1="18" x2="12.01" y2="18" />
                    </svg>
                    Mobile Item 2
                  </ReactMenuItem>
                </div>
                <ReactMenuSeparator />
                <ReactMenuItem value="always">Always Visible</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
          </div>
        </div>

        <div className="mbe4">
          <h2>Recommended: Conditional Rendering (Better Performance)</h2>
          <p className="mbe4">
            Instead of using <code>check-hidden-items</code>, you can achieve
            the same result with better performance by using conditional
            rendering. This removes hidden items from the DOM entirely.
          </p>
        </div>
        <div className="stacked mbe4">
          <div>
            <p className="mbe4">
              <strong>Current viewport:</strong>{" "}
              {isMobile ? "Mobile" : "Desktop"}
            </p>
            <ReactMenuButton menuAriaLabel="Conditional menu">
              Conditional Menu
              <ReactMenu slot="menu">
                {!isMobile && (
                  <ReactMenuItem value="desktop1">
                    <svg
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      width="16"
                      height="16"
                      className="mie2"
                      style={{
                        display: "inline-block",
                        verticalAlign: "middle",
                      }}
                    >
                      <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
                      <line x1="8" y1="21" x2="16" y2="21" />
                      <line x1="12" y1="17" x2="12" y2="21" />
                    </svg>
                    Desktop Item 1
                  </ReactMenuItem>
                )}
                {!isMobile && (
                  <ReactMenuItem value="desktop2">
                    <svg
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      width="16"
                      height="16"
                      className="mie2"
                      style={{
                        display: "inline-block",
                        verticalAlign: "middle",
                      }}
                    >
                      <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
                      <line x1="8" y1="21" x2="16" y2="21" />
                      <line x1="12" y1="17" x2="12" y2="21" />
                    </svg>
                    Desktop Item 2
                  </ReactMenuItem>
                )}
                {isMobile && (
                  <ReactMenuItem value="mobile1">
                    <svg
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      width="16"
                      height="16"
                      className="mie2"
                      style={{
                        display: "inline-block",
                        verticalAlign: "middle",
                      }}
                    >
                      <rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
                      <line x1="12" y1="18" x2="12.01" y2="18" />
                    </svg>
                    Mobile Item 1
                  </ReactMenuItem>
                )}
                {isMobile && (
                  <ReactMenuItem value="mobile2">
                    <svg
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      width="16"
                      height="16"
                      className="mie2"
                      style={{
                        display: "inline-block",
                        verticalAlign: "middle",
                      }}
                    >
                      <rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
                      <line x1="12" y1="18" x2="12.01" y2="18" />
                    </svg>
                    Mobile Item 2
                  </ReactMenuItem>
                )}
                <ReactMenuSeparator />
                <ReactMenuItem value="always">Always Visible</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
          </div>
        </div>

        <div className="mbe4">
          <h2>Dynamic Icon Switching</h2>
          <p className="mbe4">
            The menu button exposes a <code>data-menu-open</code> attribute that
            changes based on the menu state. You can use this with CSS to
            dynamically switch icons when the menu opens/closes.
          </p>
        </div>
        <div className="stacked mbe4">
          <div className="flex-inline items-center" style={{ gap: "1rem" }}>
            <ReactMenuButton
              menuVariant="icon"
              ghost
              size="sm"
              className="dynamic-icon-menu"
              ariaLabel="Toggle menu"
              menuAriaLabel="Navigation menu"
            >
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                width="14"
                height="14"
                className="menu-icon"
              >
                <line x1="3" y1="12" x2="21" y2="12" />
                <line x1="3" y1="6" x2="21" y2="6" />
                <line x1="3" y1="18" x2="21" y2="18" />
              </svg>
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                width="14"
                height="14"
                className="close-icon"
              >
                <line x1="18" y1="6" x2="6" y2="18" />
                <line x1="6" y1="6" x2="18" y2="18" />
              </svg>
              <ReactMenu slot="menu">
                <ReactMenuItem value="home">Home</ReactMenuItem>
                <ReactMenuItem value="about">About</ReactMenuItem>
                <ReactMenuItem value="contact">Contact</ReactMenuItem>
                <ReactMenuSeparator />
                <ReactMenuItem value="logout">Logout</ReactMenuItem>
              </ReactMenu>
            </ReactMenuButton>
            <p>Click the icon to see it change from ☰ to ✕</p>
          </div>
        </div>

        <div className="mbe4">
          <h2>CSS Shadow Parts Customization</h2>
        </div>
        <div className="stacked mbe4">
          <ReactMenuButton
            menuAriaLabel="Custom menu"
            className="custom-menu-button"
          >
            Custom Menu
            <ReactMenu slot="menu">
              <ReactMenuItem value="one">Option 1</ReactMenuItem>
              <ReactMenuItem value="two">Option 2</ReactMenuItem>
              <ReactMenuSeparator />
              <ReactMenuItem value="three">Option 3</ReactMenuItem>
            </ReactMenu>
          </ReactMenuButton>
        </div>
      </section>
    </>
  );
}
Open in StackBlitz

Usage

TIP

The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add Menu

The CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.

Vue
vue
<template>
  <section>
    <VueMenu
      menu-variant="chevron"
      menu-aria-label="Menu options"
    >
      Menu
      <template #menu>
        <VueMenuItem value="edit">Edit</VueMenuItem>
        <VueMenuItem value="copy">Copy</VueMenuItem>
        <VueMenuItem value="paste">Paste</VueMenuItem>
        <VueMenuSeparator />
        <VueMenuItem value="delete">Delete</VueMenuItem>
      </template>
    </VueMenu>

    <VueMenu
      menu-variant="button"
      button-variant="primary"
      size="md"
      menu-aria-label="Actions menu"
    >
      Actions
      <template #menu>
        <VueMenuItem value="new">New File</VueMenuItem>
        <VueMenuItem value="open">Open File</VueMenuItem>
        <VueMenuItem value="save">Save</VueMenuItem>
      </template>
    </VueMenu>

    <VueMenu
      menu-variant="icon"
      ghost
      unicode="⋮"
      aria-label="More options"
      menu-aria-label="More options menu"
    >
      <template #menu>
        <VueMenuItem value="settings">Settings</VueMenuItem>
        <VueMenuItem value="profile">Profile</VueMenuItem>
        <VueMenuSeparator />
        <VueMenuItem value="logout">Logout</VueMenuItem>
      </template>
    </VueMenu>

    <VueMenu
      menu-align="right"
      menu-aria-label="Right-aligned menu"
    >
      Options
      <template #menu>
        <VueMenuItem value="option1">Option 1</VueMenuItem>
        <VueMenuItem value="option2">Option 2</VueMenuItem>
        <VueMenuItem value="option3">Option 3</VueMenuItem>
      </template>
    </VueMenu>

    <VueMenu menu-aria-label="Navigation menu">
      Navigation
      <template #menu>
        <VueMenuItem value="home" href="#home">Home</VueMenuItem>
        <VueMenuItem value="about" href="#about">About</VueMenuItem>
        <VueMenuItem value="contact" href="#contact">Contact</VueMenuItem>
      </template>
    </VueMenu>

    <VueMenu
      menu-variant="button"
      button-variant="primary"
      menu-aria-label="Menu with disabled items"
    >
      Mixed States
      <template #menu>
        <VueMenuItem value="enabled1">Enabled Item</VueMenuItem>
        <VueMenuItem value="disabled1" :disabled="true">Disabled Item</VueMenuItem>
        <VueMenuItem value="enabled2">Another Enabled</VueMenuItem>
      </template>
    </VueMenu>

    <VueMenu
      menu-aria-label="Event testing menu"
      @menu-open="handleMenuOpen"
      @menu-close="handleMenuClose"
      @menu-select="handleMenuSelect"
    >
      Event Test
      <template #menu>
        <VueMenuItem value="option1">Option 1</VueMenuItem>
        <VueMenuItem value="option2">Option 2</VueMenuItem>
      </template>
    </VueMenu>
  </section>
</template>

<script>
import VueMenu, { VueMenuItem, VueMenuSeparator } from "agnosticui-core/menu/vue";

export default {
  components: {
    VueMenu,
    VueMenuItem,
    VueMenuSeparator,
  },
  methods: {
    handleMenuOpen(detail) {
      console.log("Menu opened, open:", detail.open);
    },
    handleMenuClose(detail) {
      console.log("Menu closed, open:", detail.open);
    },
    handleMenuSelect(detail) {
      console.log("Selected:", detail.value);
    },
  },
};
</script>
React
tsx
import { ReactMenuButton, ReactMenu, ReactMenuItem, ReactMenuSeparator } from 'agnosticui-core/menu/react';

export default function MenuExample() {
  const handleMenuOpen = (event) => {
    console.log("Menu opened, open:", event.detail.open);
  };

  const handleMenuClose = (event) => {
    console.log("Menu closed, open:", event.detail.open);
  };

  const handleMenuSelect = (event) => {
    console.log("Selected:", event.detail.value);
  };

  return (
    <section>
      <ReactMenuButton
        menuVariant="chevron"
        size="md"
        onMenuOpen={handleMenuOpen}
        onMenuClose={handleMenuClose}
      >
        Menu
        <ReactMenu slot="menu" ariaLabel="Menu options">
          <ReactMenuItem value="edit" onMenuSelect={handleMenuSelect}>
            Edit
          </ReactMenuItem>
          <ReactMenuItem value="copy" onMenuSelect={handleMenuSelect}>
            Copy
          </ReactMenuItem>
          <ReactMenuItem value="paste" onMenuSelect={handleMenuSelect}>
            Paste
          </ReactMenuItem>
          <ReactMenuSeparator />
          <ReactMenuItem value="delete" onMenuSelect={handleMenuSelect}>
            Delete
          </ReactMenuItem>
        </ReactMenu>
      </ReactMenuButton>

      <ReactMenuButton
        menuVariant="button"
        size="md"
        buttonVariant="primary"
      >
        Actions
        <ReactMenu slot="menu" ariaLabel="Action menu">
          <ReactMenuItem value="new">New File</ReactMenuItem>
          <ReactMenuItem value="open">Open File</ReactMenuItem>
          <ReactMenuItem value="save">Save</ReactMenuItem>
        </ReactMenu>
      </ReactMenuButton>

      <ReactMenuButton
        menuVariant="icon"
        size="md"
        ghost
        unicode="⋮"
        ariaLabel="More options"
      >
        <ReactMenu slot="menu" ariaLabel="More options menu">
          <ReactMenuItem value="settings">Settings</ReactMenuItem>
          <ReactMenuItem value="profile">Profile</ReactMenuItem>
          <ReactMenuSeparator />
          <ReactMenuItem value="logout">Logout</ReactMenuItem>
        </ReactMenu>
      </ReactMenuButton>

      <ReactMenuButton
        menuVariant="chevron"
        size="md"
        menuAlign="right"
      >
        Options
        <ReactMenu slot="menu" ariaLabel="Right-aligned menu">
          <ReactMenuItem value="option1">Option 1</ReactMenuItem>
          <ReactMenuItem value="option2">Option 2</ReactMenuItem>
          <ReactMenuItem value="option3">Option 3</ReactMenuItem>
        </ReactMenu>
      </ReactMenuButton>

      <ReactMenuButton
        menuVariant="chevron"
        size="md"
      >
        Navigation
        <ReactMenu slot="menu" ariaLabel="Navigation menu">
          <ReactMenuItem value="home" href="#home">Home</ReactMenuItem>
          <ReactMenuItem value="about" href="#about">About</ReactMenuItem>
          <ReactMenuItem value="contact" href="#contact">Contact</ReactMenuItem>
        </ReactMenu>
      </ReactMenuButton>

      <ReactMenuButton
        menuVariant="button"
        size="md"
        buttonVariant="primary"
      >
        Mixed States
        <ReactMenu slot="menu" ariaLabel="Menu with disabled items">
          <ReactMenuItem value="enabled1">Enabled Item</ReactMenuItem>
          <ReactMenuItem value="disabled1" disabled>Disabled Item</ReactMenuItem>
          <ReactMenuItem value="enabled2">Another Enabled</ReactMenuItem>
        </ReactMenu>
      </ReactMenuButton>
    </section>
  );
}
Lit (Web Components)
typescript
import { LitElement, html, css } from 'lit';
import { customElement } from 'lit/decorators.js';
import 'agnosticui-core/menu';

@customElement('menu-example')
export class MenuExample extends LitElement {
  static styles = css`
    :host {
      display: block;
    }
    section {
      display: flex;
      flex-wrap: wrap;
      gap: 1rem;
    }
  `;

  firstUpdated() {
    // Set up event listeners for menu components in the shadow DOM
    const menuButton = this.shadowRoot?.querySelector('#my-menu');

    menuButton?.addEventListener('menu-open', (e: Event) => {
      const customEvent = e as CustomEvent;
      console.log('Menu opened, open:', customEvent.detail.open);
    });

    menuButton?.addEventListener('menu-close', (e: Event) => {
      const customEvent = e as CustomEvent;
      console.log('Menu closed, open:', customEvent.detail.open);
    });

    const menuItems = this.shadowRoot?.querySelectorAll('ag-menu-item');
    menuItems?.forEach((item) => {
      item.addEventListener('menu-select', (e: Event) => {
        const customEvent = e as CustomEvent;
        console.log('Selected:', customEvent.detail.value);
      });
    });

    const menuButton2 = this.shadowRoot?.querySelector('#callback-menu') as any;
    if (menuButton2) {
      menuButton2.onMenuOpen = (e: CustomEvent) => {
        console.log('Menu opened via callback, open:', e.detail.open);
      };
      menuButton2.onMenuClose = (e: CustomEvent) => {
        console.log('Menu closed via callback, open:', e.detail.open);
      };
    }
  }

  render() {
    return html`
      <section>
        <ag-menu-button id="my-menu" menu-variant="chevron" size="md">
          Menu
          <ag-menu slot="menu" aria-label="Menu options">
            <ag-menu-item value="edit">Edit</ag-menu-item>
            <ag-menu-item value="copy">Copy</ag-menu-item>
            <ag-menu-item value="paste">Paste</ag-menu-item>
            <ag-menu-separator></ag-menu-separator>
            <ag-menu-item value="delete">Delete</ag-menu-item>
          </ag-menu>
        </ag-menu-button>

        <ag-menu-button menu-variant="button" size="md" button-variant="primary">
          Actions
          <ag-menu slot="menu" aria-label="Action menu">
            <ag-menu-item value="new">New File</ag-menu-item>
            <ag-menu-item value="open">Open File</ag-menu-item>
            <ag-menu-item value="save">Save</ag-menu-item>
            <ag-menu-separator></ag-menu-separator>
            <ag-menu-item value="exit">Exit</ag-menu-item>
          </ag-menu>
        </ag-menu-button>

        <ag-menu-button menu-variant="icon" size="md" ghost unicode="⋮" aria-label="More options">
          <ag-menu slot="menu" aria-label="More options menu">
            <ag-menu-item value="settings">Settings</ag-menu-item>
            <ag-menu-item value="profile">Profile</ag-menu-item>
            <ag-menu-separator></ag-menu-separator>
            <ag-menu-item value="logout">Logout</ag-menu-item>
          </ag-menu>
        </ag-menu-button>

        <ag-menu-button menu-variant="chevron" size="md" menu-align="right">
          Options
          <ag-menu slot="menu" aria-label="Right-aligned menu">
            <ag-menu-item value="option1">Option 1</ag-menu-item>
            <ag-menu-item value="option2">Option 2</ag-menu-item>
            <ag-menu-item value="option3">Option 3</ag-menu-item>
          </ag-menu>
        </ag-menu-button>

        <ag-menu-button menu-variant="chevron" size="md">
          Navigation
          <ag-menu slot="menu" aria-label="Navigation menu">
            <ag-menu-item value="home" href="#home">Home</ag-menu-item>
            <ag-menu-item value="about" href="#about">About</ag-menu-item>
            <ag-menu-item value="contact" href="#contact">Contact</ag-menu-item>
          </ag-menu>
        </ag-menu-button>

        <ag-menu-button menu-variant="button" size="md" button-variant="primary">
          Mixed States
          <ag-menu slot="menu" aria-label="Menu with disabled items">
            <ag-menu-item value="enabled1">Enabled Item</ag-menu-item>
            <ag-menu-item value="disabled1" disabled>Disabled Item</ag-menu-item>
            <ag-menu-item value="enabled2">Another Enabled</ag-menu-item>
          </ag-menu>
        </ag-menu-button>
      </section>
    `;
  }
}

Note: When using menu components within a custom element's shadow DOM, set up event listeners in the component's lifecycle (e.g., firstUpdated()) rather than using DOMContentLoaded, as document.querySelector() cannot access elements inside shadow DOM. Use this.shadowRoot.querySelector() instead.

Props

PropTypeDefaultDescription
menuVariant'chevron' | 'button' | 'icon''chevron'Structural variant of the menu button
menuAlign'left' | 'right''left'Horizontal alignment of the menu relative to the button
buttonVariant'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'monochrome' | ''''Color variant from AgButton
size'x-sm' | 'sm' | 'md' | 'lg' | 'xl''md'Size of the menu button (from AgButton)
shape'capsule' | 'rounded' | 'circle' | 'square' | 'rounded-square' | '''rounded'Shape of the button (from AgButton)
borderedbooleanfalseUse bordered button style (from AgButton)
ghostbooleanfalseUse ghost button style (from AgButton)
linkbooleanfalseUse link button style (from AgButton)
groupedbooleanfalsePart of a button group (from AgButton)
disabledbooleanfalseDisables the menu button
loadingbooleanfalseShow loading state (from AgButton)
ariaLabelstring''ARIA label for the menu button
ariaDescribedbystring''ARIA describedby for the menu button
unicodestring''Unicode character for icon variant
additionalGutterstring''Additional vertical spacing beyond the trigger button height when positioning the menu (e.g., '10px', '1rem')

VueMenu inherits all the properties from Menu Button above, plus:

PropTypeDefaultDescription
openbooleanfalseWhether the menu is open (controlled mode)
placement'bottom-start' | 'bottom-end' | 'top-start' | 'top-end''bottom-start'Placement of the menu popover
menuAriaLabelstring''ARIA label for the menu
menuAriaLabelledBystring''ARIA labelledby for the menu

These props apply to the <ag-menu> element itself (nested inside the button):

PropTypeDefaultDescription
type'default' | 'single-select''default'Selection behavior: 'default' for navigation (transient selection), 'single-select' for persistent selection
selectedValuestringundefinedThe currently selected item value (only applies to type="single-select")
ariaLabelstring''ARIA label for the menu
ariaLabelledBystring''ARIA labelledby for the menu
PropTypeDefaultDescription
valuestring''Unique value for the menu item
disabledbooleanfalseDisables the menu item
hrefstring''URL for menu item link
targetstring''Target for menu item link (e.g., _blank)
checkedbooleanfalseWhether the menu item is checked (for single-select menus)
variant'default' | 'monochrome''default'Visual variant of the menu item

Events

EventFrameworkDetailDescription
menu-openVue: @menu-open
React: onMenuOpen
Lit: @menu-open or .onMenuOpen
{ open: boolean }Fired when menu opens. The open property will be true.
menu-closeVue: @menu-close
React: onMenuClose
Lit: @menu-close or .onMenuClose
{ open: boolean }Fired when menu closes. The open property will be false.
clickVue: @click
React: onClick
Lit: @click or .onClick
Native MouseEventFired when the menu button is clicked (native composed event).
focusVue: @focus
React: onFocus
Lit: @focus or .onFocus
Native FocusEventFired when the menu button receives focus (re-dispatched from host).
blurVue: @blur
React: onBlur
Lit: @blur or .onBlur
Native FocusEventFired when the menu button loses focus (re-dispatched from host).
keydownVue: @keydown
React: onKeyDown
Lit: @keydown or .onKeyDown
Native KeyboardEventFired when a key is pressed on the menu button (native composed event).
EventFrameworkDetailDescription
menu-selectVue: @menu-select
React: onMenuSelect
Lit: @menu-select or .onMenuSelect
{ value: string }Fired when a menu item is selected. Contains the item's value.
clickVue: @click
React: onClick
Lit: @click or .onClick
Native MouseEventFired when the menu item is clicked (native composed event).
EventFrameworkDetailDescription
keydownVue: @menu-keydown
React: onKeyDown on menu
Lit: @keydown or .onKeyDown
Native KeyboardEventFired when a key is pressed in the menu (native composed event).

Event Patterns

AgnosticUI Menu supports three event handling patterns:

  1. addEventListener (Lit/Vanilla JS):
javascript
const menuButton = document.querySelector("ag-menu-button");
menuButton.addEventListener("menu-open", (e) => {
  console.log("Menu opened:", e.detail.open);
});
  1. Callback props (Lit/Vanilla JS):
javascript
const menuButton = document.querySelector("ag-menu-button");
menuButton.onMenuOpen = (e) => {
  console.log("Menu opened:", e.detail.open);
};
  1. Framework event handlers (Vue/React):
vue
<VueMenu @menu-open="handleMenuOpen" />
tsx
<ReactMenuButton onMenuOpen={handleMenuOpen} />

All three patterns work identically thanks to the dual-dispatch system.

Components

Vue

  • VueMenu: Main menu component with button and popover
  • VueMenuItem: Individual menu item (button or link)
  • VueMenuSeparator: Visual separator between menu items

React

  • ReactMenuButton: Menu trigger button
  • ReactMenu: Menu popover container
  • ReactMenuItem: Individual menu item (button or link)
  • ReactMenuSeparator: Visual separator between menu items

Lit

  • ag-menu-button: Menu trigger button
  • ag-menu: Menu popover container
  • ag-menu-item: Individual menu item (button or link)
  • ag-menu-separator: Visual separator between menu items

Slots

Vue

  • Default slot: Content for the menu button
  • #menu template slot: Menu items

React

  • children: Content for the menu button
  • ReactMenu with slot="menu": Menu items

Lit

  • Default slot: Content for the menu button
  • ag-menu with slot="menu": Menu items

Accessibility

The Menu component implements the WAI-ARIA Menu Button Pattern:

  • Uses role="button" and aria-haspopup="true" for the menu trigger
  • Uses role="menu" for the menu container
  • Uses role="menuitem" for individual menu items
  • Implements keyboard navigation:
    • Enter/Space: Opens the menu when focused on button
    • Arrow Up/Down: Navigates between menu items
    • Home/End: Jumps to first/last menu item
    • Enter: Selects the focused menu item
    • Escape/Tab: Closes the menu
  • Manages focus automatically:
    • Focus moves to first item when menu opens
    • Focus returns to button when menu closes
    • Disabled items are skipped during navigation
  • Supports aria-label for accessibility
  • Menu items can be marked with disabled attribute

Best Practices

  • Always provide an aria-label or menu-aria-label for accessibility
  • Use semantic grouping with MenuSeparator to organize related items
  • Prefer menu items over buttons when presenting a list of actions
  • Use the href prop for navigation items instead of click handlers
  • Keep menu item labels concise and action-oriented
  • Use the disabled prop rather than hiding items when possible
  • For icon-only buttons, always provide an aria-label

Variants

Chevron Variant (menuVariant="chevron")

The default variant shows text with a chevron indicator. Best for navigation menus or action lists.

Button Variant (menuVariant="button")

Styled as a button without the chevron. Combine with buttonVariant for color variants. Best for prominent actions.

Icon Variant (menuVariant="icon")

Displays only an icon (using unicode prop or children). Best for compact UIs or toolbar menus. Always requires an aria-label.

Button Variants (buttonVariant)

Since AgMenuButton wraps AgButton, you can use any button color variant:

  • primary - Primary action color
  • secondary - Secondary action color
  • success - Success/positive action
  • warning - Warning/caution action
  • danger - Destructive/dangerous action
  • monochrome - Neutral monochrome style
  • '' (empty) - Default/unstyled

Menu items support a variant prop for different visual styles:

  • default - Standard menu item with colored selection background
  • monochrome - Monochrome style with neutral selection colors

Button Styling Options

All AgButton properties are available:

  • size: x-sm, sm, md, lg, xl
  • shape: capsule, rounded, circle, square, rounded-square
  • bordered: Add border to button
  • ghost: Transparent background with colored text/border
  • link: Style as a link

The menuAlign prop controls the horizontal alignment of the menu relative to the trigger button:

  • left (default): Menu left edge aligns with button left edge
  • right: Menu right edge aligns with button right edge

This is particularly useful when the menu button is positioned near the right edge of the viewport, ensuring the menu stays within view.

vue
<VueMenu menu-align="left">Options</VueMenu>

<VueMenu menu-align="right">Options</VueMenu>

Placement Options

The menu popover can be positioned relative to the button:

  • bottom-start: Below button, aligned to start (default)
  • bottom-end: Below button, aligned to end
  • top-start: Above button, aligned to start
  • top-end: Above button, aligned to end

The type prop on the <ag-menu> element controls the selection behavior:

type="default" (Navigation Menus)

Use for: Navigation, actions, commands (Profile, Logout, New File, etc.)

  • Selection is transient - items show selected state only while hovering/focused
  • Selection clears automatically when the menu closes
  • No persistent checked state
  • Follows WAI-ARIA menuitem pattern
vue
<VueMenu menu-aria-label="User menu">
  User
  <template #menu>
    <VueMenuItem value="profile">Profile</VueMenuItem>
    <VueMenuItem value="settings">Settings</VueMenuItem>
    <VueMenuItem value="logout">Logout</VueMenuItem>
  </template>
</VueMenu>

type="single-select" (Selection Menus)

Use for: Persistent selections (Sort by, Filter by, Choose theme, etc.)

  • Selection is persistent - selected item shows checked state
  • Selection remains after menu closes
  • Use selectedValue prop to control which item is checked
  • Follows WAI-ARIA menuitemradio pattern
vue
<VueMenu menu-aria-label="Sort options">
  Sort by
  <template #menu>
    <ag-menu type="single-select" selected-value="date">
      <VueMenuItem value="date">Date</VueMenuItem>
      <VueMenuItem value="name">Name</VueMenuItem>
      <VueMenuItem value="size">Size</VueMenuItem>
    </ag-menu>
  </template>
</VueMenu>

Advanced Features

Additional Gutter

The additionalGutter prop allows you to add extra vertical spacing beyond the trigger button's height when positioning the menu. This is useful when the menu button is within a taller container (like a header) and you need the menu to clear that container.

React
tsx
import { ReactMenuButton, ReactMenu, ReactMenuItem } from "agnosticui-core/menu/react";

export default function HeaderMenu() {
  return (
    <header style={{ height: "60px", padding: "10px", background: "#f3f4f6" }}>
      <ReactMenuButton
        menuVariant="chevron"
        additionalGutter="10px"
        ariaLabel="Header menu"
      >
        Menu
        <ReactMenu slot="menu" ariaLabel="Header menu options">
          <ReactMenuItem value="option1">Option 1</ReactMenuItem>
          <ReactMenuItem value="option2">Option 2</ReactMenuItem>
          <ReactMenuItem value="option3">Option 3</ReactMenuItem>
        </ReactMenu>
      </ReactMenuButton>
    </header>
  );
}
Vue
vue
<template>
  <header style="height: 60px; padding: 10px; background: #f3f4f6;">
    <VueMenu
      menu-variant="chevron"
      additional-gutter="10px"
      aria-label="Header menu"
      menu-aria-label="Header menu options"
    >
      Menu
      <template #menu>
        <VueMenuItem value="option1">Option 1</VueMenuItem>
        <VueMenuItem value="option2">Option 2</VueMenuItem>
        <VueMenuItem value="option3">Option 3</VueMenuItem>
      </template>
    </VueMenu>
  </header>
</template>

<script>
import VueMenu, { VueMenuItem } from "agnosticui-core/menu/vue";

export default {
  components: { VueMenu, VueMenuItem }
};
</script>

Dynamic Icon Switching

The menu button exposes a data-menu-open attribute that changes based on the menu state. You can use this with CSS to dynamically switch icons when the menu opens/closes.

React
tsx
import { ReactMenuButton, ReactMenu, ReactMenuItem } from "agnosticui-core/menu/react";
import React from "react";

export default function DynamicIconMenu() {
  return (
    <>
      <style>
        {`
          .dynamic-icon-menu .menu-icon,
          .dynamic-icon-menu .close-icon {
            transition: opacity #6366f1 ease-in-out;
          }
          .dynamic-icon-menu[data-menu-open="false"] .close-icon {
            opacity: 0;
            pointer-events: none;
            position: absolute;
          }
          .dynamic-icon-menu[data-menu-open="true"] .menu-icon {
            opacity: 0;
            pointer-events: none;
            position: absolute;
          }
        `}
      </style>
      <ReactMenuButton
        menuVariant="icon"
        ghost
        className="dynamic-icon-menu"
        ariaLabel="Toggle menu"
      >
        <span className="menu-icon">☰</span>
        <span className="close-icon">✕</span>
        <ReactMenu slot="menu" ariaLabel="Navigation">
          <ReactMenuItem value="home">Home</ReactMenuItem>
          <ReactMenuItem value="about">About</ReactMenuItem>
          <ReactMenuItem value="contact">Contact</ReactMenuItem>
        </ReactMenu>
      </ReactMenuButton>
    </>
  );
}
Vue
vue
<template>
  <VueMenu
    menu-variant="icon"
    ghost
    class="dynamic-icon-menu"
    aria-label="Toggle menu"
    menu-aria-label="Navigation"
  >
    <span class="menu-icon">☰</span>
    <span class="close-icon">✕</span>
    <template #menu>
      <VueMenuItem value="home">Home</VueMenuItem>
      <VueMenuItem value="about">About</VueMenuItem>
      <VueMenuItem value="contact">Contact</VueMenuItem>
    </template>
  </VueMenu>
</template>

<script>
import VueMenu, { VueMenuItem } from "agnosticui-core/menu/vue";

export default {
  components: { VueMenu, VueMenuItem }
};
</script>

<style scoped>
.dynamic-icon-menu :deep(.menu-icon),
.dynamic-icon-menu :deep(.close-icon) {
  transition: opacity #6366f1 ease-in-out;
}
.dynamic-icon-menu[data-menu-open="false"] :deep(.close-icon) {
  opacity: 0;
  pointer-events: none;
  position: absolute;
}
.dynamic-icon-menu[data-menu-open="true"] :deep(.menu-icon) {
  opacity: 0;
  pointer-events: none;
  position: absolute;
}
</style>
Lit
html
<style>
  .dynamic-icon-menu .menu-icon,
  .dynamic-icon-menu .close-icon {
    transition: opacity #6366f1 ease-in-out;
  }
  .dynamic-icon-menu[data-menu-open="false"] .close-icon {
    opacity: 0;
    pointer-events: none;
    position: absolute;
  }
  .dynamic-icon-menu[data-menu-open="true"] .menu-icon {
    opacity: 0;
    pointer-events: none;
    position: absolute;
  }
</style>

<ag-menu-button
  menu-variant="icon"
  ghost
  class="dynamic-icon-menu"
  aria-label="Toggle menu"
>
  <span class="menu-icon">☰</span>
  <span class="close-icon">✕</span>
  <ag-menu slot="menu" aria-label="Navigation">
    <ag-menu-item value="home">Home</ag-menu-item>
    <ag-menu-item value="about">About</ag-menu-item>
    <ag-menu-item value="contact">Contact</ag-menu-item>
  </ag-menu>
</ag-menu-button>

CSS Shadow Parts

PartDescription
ag-menu-trigger-chevron-buttonThe menu button (chevron variant)
ag-menu-trigger-icon-buttonThe menu button (icon variant)
ag-menu-trigger-regular-buttonThe menu button (button variant)
ag-menu-labelThe label inside the chevron button
ag-menu-unicode-iconThe unicode icon in the icon button
ag-menu-chevron-iconThe chevron icon in the chevron button
ag-menuThe menu container
ag-menu-itemA menu item
ag-menu-item-buttonA menu item button
ag-menu-separatorA separator between menu items

Customization Example

css
.custom-menu-button::part(ag-menu-trigger-chevron-button) {
  background-color: #4a5568;
  color: white;
  border: 2px solid #2d3748;
  border-radius: 8px;
}
.custom-menu-button::part(ag-menu-label) {
  font-weight: bold;
}
.custom-menu-button::part(ag-menu-chevron-icon) {
  color: #a0aec0;
}
.custom-menu::part(ag-menu) {
  background-color: #2d3748;
  border: 1px solid #4a5568;
  border-radius: 8px;
}
.custom-menu::part(ag-menu-item) {
  color: #e2e8f0;
}
.custom-menu::part(ag-menu-item):hover {
  background-color: #4a5568;
}
.custom-menu::part(ag-menu-separator) {
  background-color: #4a5568;
}