Skip to content

MagicCommand

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

View source View as Markdown

or press ⌘+K / ⌃+K
DefaultDemo.vue
<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-muted">
      <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 'mirror-compat'
import DefaultView from './components/DefaultView.vue'
</script>

Anatomy

<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.

<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.

pnpm add @maas/vue-equipment

Vue

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

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.

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.

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

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

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

Peer Dependencies

MagicCommand relies on the following optional peer dependencies in addition to the core peer dependencies. Package managers don’t install optional peer dependencies automatically, so add them to your project.

Installation

pnpm add @vueuse/integrations focus-trap wheel-gestures

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
MagicCommandOptionsfalse

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

ModalDemo.vue
<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 'mirror-compat'
import DefaultView from './components/DefaultView.vue'
</script>

Drawer

DrawerDemo.vue
<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 'mirror-compat'
import DefaultView from './components/DefaultView.vue'
</script>

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