Raccourci clavier : Ctrl + K
Commencer

Palette de commandes

VCommandPalette ouvre un champ de recherche au-dessus d’une liste de commandes, dans un dialogue modal natif. La saisie filtre la liste, les flèches s’y déplacent et Entrée exécute une commande. La prop shortcut l’ouvre depuis n’importe où dans la page.

Utilisation

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VCommandPalette, VHotkeys } from 'vectis-ui'
import type { CommandPaletteItem } from 'vectis-ui'
import { add, notifications, schedule, table_chart as tableChart } from 'vectis-ui/icons'

const open = ref(false)
const last = ref('')

const items: CommandPaletteItem[] = [
  { label: 'New document', icon: add },
  { label: 'Open reports', icon: tableChart },
  { label: 'Sync now', icon: schedule },
  { label: 'Notification settings', icon: notifications },
]
</script>

<template>
  <div class="demo">
    <VCommandPalette
      v-model:open="open"
      :items="items"
      shortcut="alt+k"
      @select="(command) => (last = command.label)"
    >
      <template #trigger="{ triggerProps }">
        <VButton v-bind="triggerProps" variant="outline" tone="neutral">
          Search commands
          <VHotkeys keys="alt+k" />
        </VButton>
      </template>
    </VCommandPalette>
    <p v-if="last">Ran: {{ last }}</p>
  </div>
</template>

<style scoped>
.demo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--vectis-space-4);
}

p {
  margin: 0;
}
</style>

Exemples

Groupes

Passez des commandes, des groupes nommés et des séparateurs dans items. Les keywords sont cherchés sans être affichés, et le shortcut d’une commande est seulement affiché.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VCommandPalette } from 'vectis-ui'
import type { CommandPaletteItem } from 'vectis-ui'
import { add, info, notifications, schedule } from 'vectis-ui/icons'

const open = ref(false)
const last = ref('')

const items: CommandPaletteItem[] = [
  {
    label: 'Actions',
    commands: [
      {
        label: 'New document',
        description: 'A blank document in this workspace',
        icon: add,
        shortcut: 'mod+alt+n',
      },
      { label: 'Sync now', icon: schedule, keywords: ['refresh', 'reload'] },
      { label: 'Notification settings', icon: notifications },
    ],
  },
  { separator: true },
  {
    label: 'Help',
    commands: [{ label: 'Keyboard shortcuts', icon: info, shortcut: 'shift+/' }],
  },
]
</script>

<template>
  <div class="demo">
    <VCommandPalette
      v-model:open="open"
      :items="items"
      @select="(command) => (last = command.label)"
    >
      <template #trigger="{ triggerProps }">
        <VButton v-bind="triggerProps" variant="outline" tone="neutral">Open the palette</VButton>
      </template>
    </VCommandPalette>
    <p v-if="last">Ran: {{ last }}</p>
  </div>
</template>

<style scoped>
.demo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--vectis-space-4);
}

p {
  margin: 0;
}
</style>

Une commande avec href est un lien : Entrée le suit, et un clic modifié l’ouvre dans un nouvel onglet. Avec un routeur, appelez event.preventDefault() dans select et naviguez vous-même.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VCommandPalette } from 'vectis-ui'
import type { CommandPaletteItem } from 'vectis-ui'
import { open_in_new as openInNew } from 'vectis-ui/icons'

const open = ref(false)

const items: CommandPaletteItem[] = [
  {
    label: 'Components',
    commands: [
      { label: 'Button', href: '#button' },
      { label: 'Dialog', href: '#dialog' },
      { label: 'Drawer', href: '#drawer' },
    ],
  },
  {
    label: 'Resources',
    commands: [
      { label: 'Vue documentation', href: 'https://vuejs.org', icon: openInNew },
      { label: 'Changelog', href: '#changelog', disabled: true },
    ],
  },
]
</script>

<template>
  <VCommandPalette v-model:open="open" :items="items" placeholder="Go to a page…">
    <template #trigger="{ triggerProps }">
      <VButton v-bind="triggerProps" variant="outline" tone="neutral">Go to</VButton>
    </template>
  </VCommandPalette>
</template>

Passez filter à false et chargez les commandes sur search, émis avec un délai. loading affiche un indicateur dans le champ.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VCommandPalette } from 'vectis-ui'
import type { CommandPaletteCommand } from 'vectis-ui'
import { description } from 'vectis-ui/icons'

const DOCUMENTS = ['Roadmap 2026', 'Release plan', 'Planning poker notes', 'Team charter']

const open = ref(false)
const loading = ref(false)
const results = ref<CommandPaletteCommand[]>([])
let request = 0

function search(query: string) {
  const id = ++request
  loading.value = true
  // Stands in for a request to a server.
  setTimeout(() => {
    if (id !== request) return
    const q = query.toLowerCase()
    results.value = DOCUMENTS.filter((name) => name.toLowerCase().includes(q)).map((label) => ({
      label,
      icon: description,
    }))
    loading.value = false
  }, 400)
}
</script>

<template>
  <VCommandPalette
    v-model:open="open"
    :items="results"
    :filter="false"
    :loading="loading"
    loading-text="Searching documents…"
    placeholder="Search documents…"
    @search="search"
  >
    <template #trigger="{ triggerProps }">
      <VButton v-bind="triggerProps">Search documents</VButton>
    </template>
  </VCommandPalette>
</template>

Commandes récentes

La palette ne garde aucun historique. Retenez les choix depuis select et ajoutez un groupe de commandes récentes tant que v-model:query est vide.

vue
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { VButton, VCommandPalette } from 'vectis-ui'
import type { CommandPaletteCommand, CommandPaletteItem } from 'vectis-ui'
import { add, info, notifications, schedule } from 'vectis-ui/icons'

const STORAGE_KEY = 'recent-commands'

const COMMANDS: CommandPaletteCommand[] = [
  { id: 'new', label: 'New document', icon: add },
  { id: 'sync', label: 'Sync now', icon: schedule },
  { id: 'notifications', label: 'Notification settings', icon: notifications },
  { id: 'help', label: 'Keyboard shortcuts', icon: info },
]

const open = ref(false)
const query = ref('')
const recentIds = ref<string[]>([])

// Storage is read once mounted: it does not exist during server rendering.
onMounted(() => {
  try {
    recentIds.value = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '[]')
  } catch {
    recentIds.value = []
  }
})

const items = computed<CommandPaletteItem[]>(() => {
  const recent = recentIds.value
    .map((id) => COMMANDS.find((command) => command.id === id))
    .filter((command) => command !== undefined)
  if (query.value || recent.length === 0) return COMMANDS
  return [
    { label: 'Recent', commands: recent },
    { label: 'All commands', commands: COMMANDS },
  ]
})

function remember(command: CommandPaletteCommand) {
  const id = String(command.id)
  recentIds.value = [id, ...recentIds.value.filter((other) => other !== id)].slice(0, 3)
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(recentIds.value))
  } catch {
    // Storage may be unavailable, in a private window for instance.
  }
}
</script>

<template>
  <VCommandPalette v-model:open="open" v-model:query="query" :items="items" @select="remember">
    <template #trigger="{ triggerProps }">
      <VButton v-bind="triggerProps" variant="outline" tone="neutral">Search commands</VButton>
    </template>
  </VCommandPalette>
</template>

API

Props

PropTypeDéfaut
itemsCommandPaletteItem[]aucune
Commandes, groupes nommés et séparateurs.
shortcutstringaucune
Raccourci global qui ouvre et ferme la palette, comme mod+k. Ignoré pendant la saisie dans un autre champ. Ajoute aria-keyshortcuts au déclencheur.
filterCommandPaletteFiltertrue
Filtrage intégré insensible aux accents sur les libellés et les mots-clés, filtrage désactivé, ou fonction personnalisée recevant la commande et la recherche nettoyée.
searchDebouncenumber250
Délai en millisecondes avant d’émettre une recherche saisie. Zéro émet immédiatement.
loadingbooleanfalse
Affiche un indicateur dans le champ, et une ligne de chargement tant qu’aucune commande n’est disponible.
loadingTextstringaucune
Texte de chargement annoncé aux lecteurs d’écran. Gardez-le cohérent avec un contenu #loading personnalisé.
emptyTextstringaucune
Titre du VEmptyState par défaut, également annoncé aux lecteurs d’écran. Gardez-le cohérent avec un contenu #empty personnalisé.
placeholderstringaucune
Texte indicatif du champ de recherche. Par défaut, celui du dictionnaire.
labelstringaucune
Nom accessible de la palette et de sa liste. Par défaut, celui du dictionnaire.
searchLabelstringaucune
Nom accessible du champ de recherche. Par défaut, celui du dictionnaire.
hideFooterbooleanfalse
Masque le pied qui liste les touches.
widthnumber | stringaucune
Largeur de la palette : pixels pour un nombre, sinon une longueur CSS.
v-model:openbooleanfalse
État d’ouverture. La fermeture native met le modèle à jour.
v-model:querystring''
Texte recherché. Vidé à la fermeture de la palette.

Événements

ÉvénementType
search[query: string]
Émet le terme recherché après searchDebounce, ou immédiatement à l’ouverture. Deux termes identiques consécutifs ne sont émis qu’une fois.
select[command: CommandPaletteCommand, event: MouseEvent]
Émis avec la commande et l’événement de clic quand une commande est choisie. La palette se ferme ensuite, sauf si la commande a keepOpen.

Slots

SlotType
trigger{ triggerProps: CommandPaletteTriggerProps; }
Contrôle d’ouverture. Liez-y les triggerProps fournies.
itemCommandPaletteItemSlotProps
Libellé et description d’une ligne. Reçoit la commande et son état actif.
emptyCommandPaletteEmptySlotProps
Contenu de l’état vide. Reçoit la recherche ; définissez aussi emptyText.
loading{}
Contenu de chargement. Définissez aussi loadingText.
footer{}
Remplace le pied qui liste les touches.

Types

Types utilisés dans les tables d’API. Importez les types exportés depuis vectis-ui.

export interface BuiltinIcon {
  name: string
  paths: readonly [string] | readonly [string, string]
}
export interface CommandPaletteCommand {
  label: string
  description?: string
  keywords?: string[]
  icon?: IconSource
  shortcut?: string
  href?: string
  disabled?: boolean
  keepOpen?: boolean
  id?: string | number
}
export interface CommandPaletteEmptySlotProps {
  query: string
}
export type CommandPaletteFilter =
  boolean | ((command: CommandPaletteCommand, query: string) => boolean)
export interface CommandPaletteGroup {
  label: string
  commands: CommandPaletteCommand[]
}
export type CommandPaletteItem =
  CommandPaletteCommand | CommandPaletteGroup | CommandPaletteSeparator
export interface CommandPaletteItemSlotProps {
  command: CommandPaletteCommand
  active: boolean
}
export interface CommandPaletteSeparator {
  separator: true
}
export type CommandPaletteTriggerProps = {
  onClick: () => void
  'aria-haspopup': 'dialog'
  'aria-keyshortcuts'?: string
}
export type IconRender =
  | { path: string; viewBox?: string }
  | { component: Component; props?: Record<string, unknown> }
  | { src: string }
  | { text: string; class?: string }
  | { class: string }
export type IconSource = string | BuiltinIcon | IconRender

Variables CSS

TokenValeur
--vectis-control-size-command-palette-width40rem
--vectis-control-size-command-palette-list-max-block20rem
--vectis-control-size-command-palette-offset6rem