Skip to content

GkMenu

A floating menu teleported to body: v-model open state, #activator slot with props / is-open for aria-haspopup, aria-expanded, aria-controls, fixed top / left from useMenuAnchorPosition, outside mousedown dismiss, Escape to close, optional scrim, and closeOnContentClick.

This is intentionally smaller than Vuetify’s VMenu: no VOverlay activator stack, nested submenu parent chain, openDelay / closeDelay, locationStrategy / scrollStrategy, or Tab focus cycling through the whole document — submenu is reserved for a future phase.

When to use

Use for short action lists anchored to an activator (button, icon trigger, contextual controls). Keep menu items action-oriented and avoid long-form content.

Live demo

API

Props

PropTypeDefaultDescription
modelValuebooleanfalseOpen state; use v-model
placement'bottom-start' | 'bottom-end' | 'top-start' | 'top-end''bottom-start'Panel position relative to the activator
offsetnumber4Gap between activator and panel (px)
closeOnContentClickbooleantrueClicking the panel closes the menu (use @click.stop on items to keep it open)
showScrimbooleanfalseFull-screen scrim (uses --gk-menu-scrim)
persistentbooleanfalseOutside click and Escape do not close
disabledbooleanfalsePrevents opening
tostring | HTMLElement'body'Teleport target
zIndexnumber | string--gk-menu-z-indexLayer stacking
restoreFocusbooleantrueFocus first focusable in the panel on open; restore previous focus on close
submenubooleanfalseReserved (no nested menu wiring in v1)

Additional attributes are applied to the panel (not the activator).

Slots

SlotSlot propsDescription
activatorprops, is-openBind v-bind="props" on your control (typically type="button").
defaultMenu content. For role="menu" on the panel, use direct role="menuitem" (or group/menuitemcheckbox) children per WAI-ARIA.

Events

EventPayloadDescription
update:modelValuebooleanOpen state
click:outsideMouseEventOutside mousedown (not emitted when persistent)
afterEnterTransition finished entering
afterLeaveTransition finished leaving

Composable

useMenuAnchorPosition is exported from god-kit/vue for custom anchored panels; GkMenuPlacement is the placement union type.

Tokens

TokenPurpose
--gk-menu-z-indexLayer stacking (2100)
--gk-menu-min-widthMinimum panel width
--gk-menu-max-heightmax-height + scroll
--gk-menu-shadowPanel shadow
--gk-menu-scrimScrim background (default transparent)

Examples

Basic

vue
<script setup lang="ts">
import { ref } from 'vue'
import { GkButton, GkMenu } from 'god-kit/vue'

const open = ref(false)
</script>

<template>
  <GkMenu v-model="open" placement="bottom-start">
    <template #activator="{ props }">
      <GkButton type="button" v-bind="props">Open</GkButton>
    </template>
    <button type="button" role="menuitem" @click.stop="open = false">Profile</button>
    <button type="button" role="menuitem">Settings</button>
  </GkMenu>
</template>

Advanced

vue
<GkMenu
  v-model="open"
  placement="top-end"
  :offset="8"
  :close-on-content-click="false"
>
  <!-- activator + menu content -->
</GkMenu>

Edge case

vue
<GkMenu
  v-model="open"
  persistent
  show-scrim
>
  <!-- use Escape and explicit close controls -->
</GkMenu>

Accessibility notes

  • Use v-bind=\"props\" from the activator slot to preserve aria-haspopup / aria-expanded wiring.
  • Keep focusable menu items as direct children with role=\"menuitem\" (or related menuitem roles).
  • ArrowUp/ArrowDown and Home/End navigation should be tested in custom slot content.

Out of scope (v1)

Nested submenu registration, router integration, retainFocus / full Tab trap parity with Vuetify, openDelay / closeDelay, and scroll / location strategies beyond anchor + viewport clamping.

Released under the MIT License.