diff --git a/packages/api-generator/src/locale/en/VCommandPalette.json b/packages/api-generator/src/locale/en/VCommandPalette.json
index 7c036d66fba..39d9929ac4d 100644
--- a/packages/api-generator/src/locale/en/VCommandPalette.json
+++ b/packages/api-generator/src/locale/en/VCommandPalette.json
@@ -20,13 +20,13 @@
},
"slots": {
"prepend": "Content to render above the search input, inside the command palette card. Useful for headers, breadcrumbs, or instructions.",
- "input": "Custom search input field. Replaces the default **v-text-field**. Useful for providing a completely custom search implementation.",
+ "input": "Custom search input field. Replaces the default **v-text-field**. Bind the slot's **props** to the input to keep keyboard navigation and screen reader announcements, and connect it to **v-model:search** for filtering.",
"input.append-inner": "Content appended inside the search input field. Useful for adding icons or buttons within the input.",
"append": "Content to render below the items list, inside the command palette card. Useful for footers, keyboard shortcut hints, or additional actions.",
"no-data": "Custom content to display when no items match the search query. Replaces the default no-data message.",
"list.prepend": "Content to render above the items list, inside the command palette card.",
"list.subheader": "Slot for customizing the rendering of subheader items in the list.",
- "item": "Replaces the rendering of each item. Bind **item** to a **v-list-item** (`v-bind=\"item\"`) to keep the keyboard highlight and screen reader announcements.",
+ "item": "Replaces the rendering of each item. Bind the slot's **props** to a **v-list-item** to keep the keyboard highlight, click handling and screen reader announcements.",
"item.prepend": "Content to display before each item's title.",
"item.title": "Slot for customizing the title of each item.",
"item.append": "Content to display after each item's title, replaces hotkey (if provided)."
diff --git a/packages/docs/src/examples/v-command-palette/slot-input.vue b/packages/docs/src/examples/v-command-palette/slot-input.vue
new file mode 100644
index 00000000000..a5ac3ddfb44
--- /dev/null
+++ b/packages/docs/src/examples/v-command-palette/slot-input.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
diff --git a/packages/docs/src/pages/en/components/command-palettes.md b/packages/docs/src/pages/en/components/command-palettes.md
index 50900aba348..fba98a3234d 100644
--- a/packages/docs/src/pages/en/components/command-palettes.md
+++ b/packages/docs/src/pages/en/components/command-palettes.md
@@ -83,6 +83,18 @@ By default, selecting an actionable item closes the palette. Use **close-on-sele
The command palette provides several slots for customizing the display of items and other elements.
+#### Input
+
+Use the **#input** slot to replace the search field. Bind the slot's **props** to your input for keyboard navigation and screen reader support, and connect it to **v-model:search** so typing filters the items.
+
+
+
+#### Item
+
+Use the **#item** slot to render each item yourself. It receives the original **item**, its **index**, and **props** to bind to a `v-list-item`, which keep the keyboard highlight, click handling and screen reader announcements.
+
+
+
#### Item prepend
Use the **#item.prepend** slot to customize the prepend area of each item. This slot receives the current **item** and **index** as slot props.
@@ -122,4 +134,4 @@ The `v-command-palette` component follows accessibility best practices:
- Returns focus to the previously focused element on close
- Supports full keyboard navigation without mouse interaction
-A custom input provided through the **#input** slot doesn't receive these attributes or the keyboard handling.
+When replacing the search field through the **#input** slot, bind the slot's **props** to your input so it keeps the keyboard handling and these attributes. The same applies to the **#item** slot: bind its **props** to a `v-list-item`.
diff --git a/packages/vuetify/src/labs/VCommandPalette/VCommandPalette.tsx b/packages/vuetify/src/labs/VCommandPalette/VCommandPalette.tsx
index 8005d79fa53..4651f8aa351 100644
--- a/packages/vuetify/src/labs/VCommandPalette/VCommandPalette.tsx
+++ b/packages/vuetify/src/labs/VCommandPalette/VCommandPalette.tsx
@@ -2,7 +2,7 @@
import './VCommandPalette.scss'
// Components
-import { VCommandPaletteSymbol } from './shared'
+import { listItemKeys, VCommandPaletteSymbol } from './shared'
import { VCommandPaletteItem } from './VCommandPaletteItem'
import { VDialog } from '@/components/VDialog'
import { makeVDialogProps } from '@/components/VDialog/VDialog'
@@ -21,7 +21,15 @@ import { useProxiedModel } from '@/composables/proxiedModel'
// Utilities
import { computed, nextTick, onUnmounted, provide, ref, shallowRef, toRef, useId, watch, watchEffect } from 'vue'
import { isActionItem } from './types'
-import { convertToUnit, genericComponent, getActiveElement, isFunction, omit, propsFactory, useRender } from '@/util'
+import {
+ convertToUnit,
+ genericComponent,
+ getActiveElement,
+ omit,
+ pick,
+ propsFactory,
+ useRender,
+} from '@/util'
// Types
import type { PropType, Ref } from 'vue'
@@ -74,12 +82,12 @@ export type VCommandPaletteSlots = {
default: never
prepend: never
append: never
- input: never
+ input: { props: Record }
'input.append-inner': never
'no-data': never
'list.prepend': never
'list.subheader': VListChildrenSlots['subheader']
- item: { item: VCommandPaletteItemType, index: number }
+ item: { item: VCommandPaletteItemType, index: number, props: Record }
'item.prepend': { item: VCommandPaletteItemType, index: number }
'item.title': { item: VCommandPaletteItemType, index: number }
'item.append': { item: VCommandPaletteItemType, index: number }
@@ -105,7 +113,7 @@ export const VCommandPalette = genericComponent()({
const { t } = useLocale()
const isOpen = useProxiedModel(props, 'modelValue')
const searchQuery = useProxiedModel(props, 'search') as Ref
- const searchInputRef = ref()
+ const inputContainerRef = ref()
const dialogRef = ref()
const listRef = ref()
const listId = `v-command-palette-list-${useId()}`
@@ -259,9 +267,7 @@ export const VCommandPalette = genericComponent()({
// Use requestAnimationFrame to ensure DOM is fully rendered
nextTick(() => {
requestAnimationFrame(() => {
- if (searchInputRef.value && isFunction(searchInputRef.value.focus)) {
- searchInputRef.value.focus()
- }
+ inputContainerRef.value?.querySelector('[autofocus]')?.focus()
})
})
} else if (!newValue && oldValue) {
@@ -280,6 +286,15 @@ export const VCommandPalette = genericComponent()({
useRender(() => {
const dialogProps = VDialog.filterProps(omit(props, ['modelValue', 'class', 'style']))
const hasResults = filteredItems.value.length > 0
+ const inputProps = {
+ role: 'combobox',
+ 'aria-expanded': hasResults,
+ 'aria-controls': hasResults ? listId : undefined,
+ 'aria-autocomplete': 'list',
+ 'aria-activedescendant': listRef.value?.activeDescendant,
+ autofocus: true,
+ onKeydown: handleSearchKeydown,
+ }
return (
()({
>
{ slots.prepend?.() }
-