Skip to content
Sponsored by

MagicCommand

MagicCommand is a flexible collection of components intended to build command palette style menus, such as Spotlight, Raycast and the like.

or press ⌘+K / ⌃+K
<template>
  <magic-command-provider id="magic-command-demo" class="flex flex-col gap-6">
    <magic-command-trigger view-id="initial-default-view" as-child>
      <m-button>Open Menu</m-button>
    </magic-command-trigger>

    <div class="type-surface-body-sm text-surface-subtle">
      <span>or press ⌘+K / ⌃+K</span>
    </div>

    <magic-command-modal :options="{ focusTrap: false }">
      <m-menu-box class="h-[30rem] w-[40rem]">
        <magic-command-renderer />
      </m-menu-box>
    </magic-command-modal>

    <default-view id="initial-default-view" />
  </magic-command-provider>
</template>

<script lang="ts" setup>
import { MButton, MMenuBox } from '@maas/mirror/vue'
import DefaultView from './components/DefaultView.vue'
</script>

Overview

Anatomy

vue
<template>
  <magic-command-provider id="your-command-id">
    <!-- modal -->
    <magic-command-modal>
      <magic-command-renderer />
    </magic-command-modal>

    <!-- drawer -->
    <magic-command-drawer>
      <magic-command-renderer />
    </magic-command-drawer>

    <!-- initial view -->
    <magic-command-view :initial="true" id="your-view-id">
      <magic-command-trigger>Open Menu</magic-command-trigger>

      <magic-command-content>
        <magic-command-item>
          <!-- your content -->
        </magic-command-item>

        <!-- nested view -->
        <magic-command-item>
          <magic-command-view>
            <magic-command-trigger>Open View</magic-command-trigger>
            <magic-command-content>
              <!-- your content -->
            </magic-command-content>
          </magic-command-view>
        </magic-command-item>
      </magic-command-content>
    </magic-command-view>

    <!-- optional -->
    <magic-command-trigger view-id="your-view-id">
      Open Menu
    </magic-command-trigger>
  </magic-command-provider>
</template>

<script setup>
const { open, close } = useMagicCommand('your-command-id')
</script>

Custom Layout

Both MagicCommandModal and MagicCommandDrawer render a default layout out of the box. Use the #layout slot to replace the entire inner structure when you need full control. For example to remove the backdrop or customise the teleport target.

vue
<template>
  <magic-command-modal>
    <template #layout>
      <magic-modal-teleport>
        <magic-modal-content>
          <magic-command-renderer />
        </magic-modal-content>
      </magic-modal-teleport>
    </template>
  </magic-command-modal>
</template>

See MagicModal and MagicDrawer for details.

Installation

CLI

Add @maas/vue-equipment to your dependencies.

sh
pnpm install @maas/vue-equipment
sh
npm install @maas/vue-equipment
sh
yarn add @maas/vue-equipment
sh
bun install @maas/vue-equipment

Vue

If you are using Vue, import and add MagicCommandPlugin to your app.

js
import { createApp } from 'vue'
import { MagicCommandPlugin } from '@maas/vue-equipment/plugins/MagicCommand'

const app = createApp({})

app.use(MagicCommandPlugin)

Nuxt

The command palette is available as a Nuxt module. In your Nuxt config file add @maas/vue-equipment/nuxt to your modules and add MagicCommand to the plugins in your configuration.

js
export default defineNuxtConfig({
  modules: ['@maas/vue-equipment/nuxt'],
  vueEquipment: {
    plugins: ['MagicCommand'],
  },
})

Composable

In order to interact with the command palette from anywhere within your app, we provide a useMagicCommand composable. Import it directly when needed.

js
import { useMagicCommand } from '@maas/vue-equipment/plugins/MagicCommand'

const { selectView } = useMagicCommand('your-command-id')

function handleClick() {
  selectView('your-view-id')
}

TIP

If you have installed the command palette as a Nuxt module, the composable will be auto-imported and is automatically available in your Nuxt app.

Peer Dependencies

If you haven’t installed the required peer dependencies automatically, you’ll need to install the following packages manually.

Installation

sh
pnpm install @nuxt/kit @maas/vue-primitive @vueuse/core defu
sh
npm install @nuxt/kit @maas/vue-primitive @vueuse/core defu
sh
yarn add @nuxt/kit @maas/vue-primitive @vueuse/core defu
sh
bun install @nuxt/kit @maas/vue-primitive @vueuse/core defu

API Reference

MagicCommandProvider

The MagicCommandProvider wraps the command palette and configures all child components according to the provided options.

Props

PropTypeRequired
id
MaybeRef<string>true
asChild
booleanfalse
options
MagicMenuOptionsfalse

Options

To customize the command palette, override the necessary options. Any custom options will be merged with the default options.

OptionTypeDefault
debug
booleanfalse
transition.content
stringmagic-command-content
keyListener.open
string[] | false['Cmd+k', 'Ctrl+k']
keyListener.close
string[] | false['Escape']
keyListener.next
string[] | false['ArrowDown']
keyListener.prev
string[] | false['ArrowUp']
keyListener.enter
string[] | false['Enter']
loop
booleanfalse

MagicCommandView

Groups command items into a single panel and tracks which item is currently selected.

Props

PropTypeRequired
id
MaybeRef<string>false
initial
booleanfalse

Slot Props

PropTypeDescription
view-activebooleanWhether the command palette view is currently open.

MagicCommandDrawer

Wraps the command palette in a MagicDrawerProvider and renders teleport, backdrop and content by default. Override the full inner structure using the #layout slot.

Props

PropTypeRequired
Options
MagicCommandDrawerOptionsfalse

MagicCommandModal

Wraps the command palette in a MagicModalProvider and renders teleport, backdrop and content by default. Override the full inner structure using the #layout slot.

Props

PropTypeRequired
Options
MagicCommandModalOptionsfalse

MagicCommandRenderer

Renders command items dynamically from registered commands.

CSS Variables

VariableDefault
--magic-command-renderer-width100%
--magic-command-renderer-height100%

MagicCommandItem

A single interactive command entry.

Props

PropTypeRequired
id
stringfalse
disabled
booleanfalse

CSS Variables

VariableDefault
--magic-command-item-cursordefault

Slot Props

PropTypeDescription
item-activebooleanWhether the item is currently selected.
item-disabledbooleanWhether the item is currently disabled.

MagicCommandTrigger

Opens or closes the command palette on click.

Props

PropTypeRequired
viewId
stringfalse
disabled
booleanfalse
trigger
Interaction[]
false
action
Action
false
asChild
booleanfalse
VariableDefault
--magic-command-trigger-cursorpointer

CSS Variables

VariableDefault
--magic-command-trigger-cursorpointer

Slot Props

PropTypeDescription
view-activebooleanWhether the command palette is currently open.
trigger-disabledbooleanWhether the trigger is currently disabled.

Errors

SourceError CodeMessage
MagicCommandDrawermissing_instance_idMagicCommandDrawer must be nested inside MagicCommandProvider
MagicCommandTriggermissing_instance_idMagicCommandTrigger must be nested inside MagicCommandProvider
MagicCommandTriggermissing_view_idMagicCommandTrigger must be nested inside MagicCommandView or a viewId must be provided
MagicCommandViewmissing_instance_idMagicCommandView must be nested inside MagicCommandProvider
MagicCommandContentmissing_instance_idMagicCommandContent must be nested inside MagicCommandProvider
MagicCommandContentmissing_view_idMagicCommandContent must be nested inside MagicCommandView
useMagicCommandview_id_requiredviewId is required to select an item
useMagicCommandid_requiredid is required to select an item
useMenuItemview_id_not_foundView {viewId} not found
useMenuKeyListenermenu_not_activeMagicMenu {state.id} is not active

Examples

<template>
  <magic-command-provider
    id="magic-command-modal-demo"
    :options="{ keyListener: { open: false } }"
  >
    <magic-command-trigger view-id="initial-modal-view" as-child>
      <m-button>Open Menu</m-button>
    </magic-command-trigger>

    <magic-command-modal :options="{ focusTrap: false }">
      <m-menu-box class="h-[30rem] w-[40rem]">
        <magic-command-renderer />
      </m-menu-box>
    </magic-command-modal>

    <default-view id="initial-modal-view" />
  </magic-command-provider>
</template>

<script lang="ts" setup>
import { MButton, MMenuBox } from '@maas/mirror/vue'
import DefaultView from './components/DefaultView.vue'
</script>

Drawer

<template>
  <magic-command-provider
    id="magic-command-drawer-demo"
    :options="{ keyListener: { open: false } }"
  >
    <magic-command-trigger view-id="initial-drawer-view" as-child>
      <m-button>Open Menu</m-button>
    </magic-command-trigger>

    <magic-command-drawer :options="{ focusTrap: false }">
      <div
        class="h-full w-full p-2 pb-[calc(var(--magic-drawer-drag-overshoot)+0.5rem)]"
      >
        <m-menu-box class="h-full w-full">
          <magic-command-renderer />
        </m-menu-box>
      </div>
    </magic-command-drawer>

    <default-view id="initial-drawer-view" />
  </magic-command-provider>
</template>

<script lang="ts" setup>
import { MButton, MMenuBox } from '@maas/mirror/vue'
import DefaultView from './components/DefaultView.vue'
</script>

<style>
[data-id='magic-command-drawer-demo'] {
  --magic-drawer-width: 40rem;
  --magic-drawer-height: 30rem;
}
</style>