Command
Fast, filterable command palette (⌘K) with groups, shortcuts and an empty state. Can open as a dialog from anywhere.
Command Palette
Search for a command to run...
import { Button } from "@/components/ui/button";import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,} from "@/components/ui/command";import { Kbd } from "@/components/ui/kbd";import { useEffect, useState } from "react";import { IconPlaceholder } from "@/edmi/icon-placeholder";
export default function Demo() { const [open, setOpen] = useState(false); useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((o) => !o); } }; document.addEventListener("keydown", down); return () => document.removeEventListener("keydown", down); }, []); return ( <div className="flex flex-col items-start gap-4"> <Command className="w-[420px] rounded-xl border border-border"> <CommandInput placeholder="Search indexes and actions…" /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Indexes"> <CommandItem>MAG4 · Magnificent Four</CommandItem> <CommandItem>MAG7 · equal weight</CommandItem> </CommandGroup> <CommandSeparator /> <CommandGroup heading="Actions"> <CommandItem> <IconPlaceholder lucide="PlusIcon" tabler="IconPlus" hugeicons="PlusSignIcon" phosphor="PlusIcon" remixicon="RiAddLine" />{" "} Create index <CommandShortcut>⌘N</CommandShortcut> </CommandItem> <CommandItem> <IconPlaceholder lucide="SparklesIcon" tabler="IconSparkles" hugeicons="SparklesIcon" phosphor="SparkleIcon" remixicon="RiSparklingLine" />{" "} Ask agent <CommandShortcut>⌘J</CommandShortcut> </CommandItem> </CommandGroup> </CommandList> </Command> <Button variant="outline" onClick={() => setOpen(true)}> Open palette <Kbd>⌘K</Kbd> </Button> <CommandDialog open={open} onOpenChange={setOpen}> <CommandInput placeholder="Type a command…" /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Actions"> <CommandItem onSelect={() => setOpen(false)}> Create index </CommandItem> <CommandItem onSelect={() => setOpen(false)}> Toggle theme </CommandItem> </CommandGroup> </CommandList> </CommandDialog> </div> );}<script setup lang="ts">import { PlusIcon, SparklesIcon } from "@lucide/vue";import { onMounted, onUnmounted, ref } from "vue";import { Button } from "@/components/ui/button";import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,} from "@/components/ui/command";import { Kbd } from "@/components/ui/kbd";
const open = ref(false);
function onKeydown(e: KeyboardEvent) { if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); open.value = !open.value; }}onMounted(() => document.addEventListener("keydown", onKeydown));onUnmounted(() => document.removeEventListener("keydown", onKeydown));</script>
<template> <div class="flex flex-col items-start gap-4"> <Command class="w-[420px] rounded-xl border border-border"> <CommandInput placeholder="Search indexes and actions…" /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Indexes"> <CommandItem value="mag4">MAG4 · Magnificent Four</CommandItem> <CommandItem value="mag7">MAG7 · equal weight</CommandItem> </CommandGroup> <CommandSeparator /> <CommandGroup heading="Actions"> <CommandItem value="create-index"> <PlusIcon /> Create index <CommandShortcut>⌘N</CommandShortcut> </CommandItem> <CommandItem value="ask-agent"> <SparklesIcon /> Ask agent <CommandShortcut>⌘J</CommandShortcut> </CommandItem> </CommandGroup> </CommandList> </Command> <Button variant="outline" @click="open = true"> Open palette <Kbd>⌘K</Kbd> </Button> <CommandDialog v-model:open="open"> <CommandInput placeholder="Type a command…" /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Actions"> <CommandItem value="create-index" @select="open = false">Create index</CommandItem> <CommandItem value="toggle-theme" @select="open = false">Toggle theme</CommandItem> </CommandGroup> </CommandList> </CommandDialog> </div></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import { Button } from "$lib/components/ui/button"; import * as Command from "$lib/components/ui/command"; import { Kbd } from "$lib/components/ui/kbd";
let open = $state(false);
function onKeydown(e: KeyboardEvent) { if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); open = !open; } }</script>
<svelte:document onkeydown={onKeydown} />
<div class="flex flex-col items-start gap-4"> <Command.Root class="w-[420px] rounded-xl border border-border"> <Command.Input placeholder="Search indexes and actions…" /> <Command.List> <Command.Empty>No results found.</Command.Empty> <Command.Group heading="Indexes"> <Command.Item>MAG4 · Magnificent Four</Command.Item> <Command.Item>MAG7 · equal weight</Command.Item> </Command.Group> <Command.Separator /> <Command.Group heading="Actions"> <Command.Item> <IconPlaceholder lucide="PlusIcon" tabler="IconPlus" hugeicons="PlusSignIcon" phosphor="PlusIcon" remixicon="RiAddLine" /> Create index <Command.Shortcut>⌘N</Command.Shortcut> </Command.Item> <Command.Item> <IconPlaceholder lucide="SparklesIcon" tabler="IconSparkles" hugeicons="SparklesIcon" phosphor="SparkleIcon" remixicon="RiSparklingLine" /> Ask agent <Command.Shortcut>⌘J</Command.Shortcut> </Command.Item> </Command.Group> </Command.List> </Command.Root> <Button variant="outline" onclick={() => (open = true)}> Open palette <Kbd>⌘K</Kbd> </Button> <Command.Dialog bind:open> <Command.Input placeholder="Type a command…" /> <Command.List> <Command.Empty>No results found.</Command.Empty> <Command.Group heading="Actions"> <Command.Item onSelect={() => (open = false)}>Create index</Command.Item> <Command.Item onSelect={() => (open = false)}>Toggle theme</Command.Item> </Command.Group> </Command.List> </Command.Dialog></div>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/commandpnpm dlx shadcn@latest add @edmi-ui/commandyarn dlx shadcn@latest add @edmi-ui/commandbunx shadcn@latest add @edmi-ui/commandPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install cmdkTerminal window pnpm add cmdkTerminal window yarn add cmdkTerminal window bun add cmdkAdd the Edmi components this one builds on:
dialog.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/commandpnpm dlx shadcn-vue@latest add @edmi-ui/commandyarn dlx shadcn-vue@latest add @edmi-ui/commandbunx shadcn-vue@latest add @edmi-ui/commandPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui @vueuse/coreTerminal window pnpm add reka-ui @vueuse/coreTerminal window yarn add reka-ui @vueuse/coreTerminal window bun add reka-ui @vueuse/coreAdd the Edmi components this one builds on:
dialog.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/command.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/command.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/command.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/command.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
dialog.Copy the code from Component source into your project and update the import paths to match your aliases.
import { Button } from "@/components/ui/button";import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,} from "@/components/ui/command";import { Kbd } from "@/components/ui/kbd";
<div className="flex flex-col items-start gap-4"> <Command className="w-[420px] rounded-xl border border-border"> <CommandInput placeholder="Search indexes and actions…" /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Indexes"> <CommandItem>MAG4 · Magnificent Four</CommandItem> <CommandItem>MAG7 · equal weight</CommandItem> </CommandGroup> <CommandSeparator /> <CommandGroup heading="Actions"> <CommandItem> <PlusIcon />{" "} Create index <CommandShortcut>⌘N</CommandShortcut> </CommandItem> <CommandItem> <SparkleIcon />{" "} Ask agent <CommandShortcut>⌘J</CommandShortcut> </CommandItem> </CommandGroup> </CommandList> </Command> <Button variant="outline"> Open palette <Kbd>⌘K</Kbd> </Button> {/* … */}</div><script setup lang="ts">import { Button } from "@/components/ui/button";import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,} from "@/components/ui/command";import { Kbd } from "@/components/ui/kbd";</script>
<template> <div class="flex flex-col items-start gap-4"> <Command class="w-[420px] rounded-xl border border-border"> <CommandInput placeholder="Search indexes and actions…" /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Indexes"> <CommandItem value="mag4">MAG4 · Magnificent Four</CommandItem> <CommandItem value="mag7">MAG7 · equal weight</CommandItem> </CommandGroup> <CommandSeparator /> <CommandGroup heading="Actions"> <CommandItem value="create-index"> <PlusIcon /> Create index <CommandShortcut>⌘N</CommandShortcut> </CommandItem> <CommandItem value="ask-agent"> <SparklesIcon /> Ask agent <CommandShortcut>⌘J</CommandShortcut> </CommandItem> </CommandGroup> </CommandList> </Command> <Button variant="outline"> Open palette <Kbd>⌘K</Kbd> </Button> <!-- … --> </div></template><script lang="ts"> import * as Command from "$lib/components/ui/command";</script>
<svelte:document onkeydown={onKeydown} /><!-- … -->Component source
Section titled “Component source”"use client";
import { Command as CommandPrimitive } from "cmdk";import { cn } from "cn";import type * as React from "react";import { MagnifyingGlassIcon, CheckIcon } from "@phosphor-icons/react";import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle,} from "@/registry/edmi/ui/dialog";
function Command({ className, ...props}: React.ComponentProps<typeof CommandPrimitive>) { return ( <CommandPrimitive data-slot="command" className={cn( "flex size-full flex-col overflow-hidden rounded-xl bg-popover text-popover-foreground", className, )} {...props} /> );}
function CommandDialog({ title = "Command Palette", description = "Search for a command to run...", children, className, showCloseButton = false, ...props}: Omit<React.ComponentProps<typeof Dialog>, "children"> & { title?: string; description?: string; className?: string; showCloseButton?: boolean; children: React.ReactNode;}) { return ( <Dialog {...props}> <DialogHeader className="sr-only"> <DialogTitle>{title}</DialogTitle> <DialogDescription>{description}</DialogDescription> </DialogHeader> <DialogContent className={cn( "top-1/3 translate-y-0 overflow-hidden p-0 sm:max-w-xl", className, )} showCloseButton={showCloseButton} > <Command className="rounded-none">{children}</Command> </DialogContent> </Dialog> );}
function CommandInput({ className, ...props}: React.ComponentProps<typeof CommandPrimitive.Input>) { return ( <div data-slot="command-input-wrapper" className="flex h-12 items-center gap-2.5 border-b border-border px-3.5" > <MagnifyingGlassIcon className="size-4 shrink-0 text-muted-foreground" /> <CommandPrimitive.Input data-slot="command-input" className={cn( "h-full w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", className, )} {...props} /> </div> );}
function CommandList({ className, ...props}: React.ComponentProps<typeof CommandPrimitive.List>) { return ( <CommandPrimitive.List data-slot="command-list" className={cn( "no-scrollbar max-h-80 scroll-py-1.5 overflow-x-hidden overflow-y-auto p-1.5 outline-none", className, )} {...props} /> );}
function CommandEmpty({ className, ...props}: React.ComponentProps<typeof CommandPrimitive.Empty>) { return ( <CommandPrimitive.Empty data-slot="command-empty" className={cn("py-6 text-center text-sm", className)} {...props} /> );}
function CommandGroup({ className, ...props}: React.ComponentProps<typeof CommandPrimitive.Group>) { return ( <CommandPrimitive.Group data-slot="command-group" className={cn( "overflow-hidden text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:pt-1.5 **:[[cmdk-group-heading]]:pb-1 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-semibold **:[[cmdk-group-heading]]:text-muted-foreground", className, )} {...props} /> );}
function CommandSeparator({ className, ...props}: React.ComponentProps<typeof CommandPrimitive.Separator>) { return ( <CommandPrimitive.Separator data-slot="command-separator" className={cn("-mx-1.5 my-1 h-px bg-border", className)} {...props} /> );}
function CommandItem({ className, children, ...props}: React.ComponentProps<typeof CommandPrimitive.Item>) { return ( <CommandPrimitive.Item data-slot="command-item" className={cn( "group/command-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-45 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground", className, )} {...props} > {children} <CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" /> </CommandPrimitive.Item> );}
function CommandShortcut({ className, ...props}: React.ComponentProps<"span">) { return ( <span data-slot="command-shortcut" className={cn( "ml-auto font-mono text-[11.5px] tracking-wide text-muted-foreground", className, )} {...props} /> );}
export { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,};<script setup lang="ts">import type { ListboxRootEmits, ListboxRootProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { ListboxRoot, useFilter, useForwardPropsEmits } from "reka-ui"import { reactive, ref, watch } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { provideCommandContext } from "."
const props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes["class"] }>(), { modelValue: "", highlightOnHover: true,})
const emits = defineEmits<ListboxRootEmits>()
const delegatedProps = reactiveOmit(props, "class")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const allItems = ref<Map<string, string>>(new Map())const allGroups = ref<Map<string, Set<string>>>(new Map())
const { contains } = useFilter({ sensitivity: "base" })const filterState = reactive({ search: "", filtered: { /** The count of all visible items. */ count: 0, /** Map from visible item id to its search score. */ items: new Map() as Map<string, number>, /** Set of groups with at least one visible item. */ groups: new Set() as Set<string>, },})
function filterItems() { if (!filterState.search) { filterState.filtered.count = allItems.value.size // Do nothing, each item will know to show itself because search is empty return }
// Reset the groups filterState.filtered.groups = new Set() let itemCount = 0
// Check which items should be included for (const [id, value] of allItems.value) { const score = contains(value, filterState.search) filterState.filtered.items.set(id, score ? 1 : 0) if (score) itemCount++ }
// Check which groups have at least 1 item shown for (const [groupId, group] of allGroups.value) { for (const itemId of group) { if (filterState.filtered.items.get(itemId)! > 0) { filterState.filtered.groups.add(groupId) break } } }
filterState.filtered.count = itemCount}
watch(() => filterState.search, () => { filterItems()})
provideCommandContext({ allItems, allGroups, filterState,})</script>
<template> <ListboxRoot data-slot="command" v-bind="forwarded" :class="cn('flex size-full flex-col overflow-hidden rounded-xl bg-popover text-popover-foreground', props.class)" > <slot /> </ListboxRoot></template><script setup lang="ts">import type { DialogRootEmits, DialogRootProps } from "reka-ui"import { useForwardPropsEmits } from "reka-ui"import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/registry/edmi/ui/dialog"import Command from "./Command.vue"
const props = withDefaults(defineProps<DialogRootProps & { title?: string description?: string}>(), { title: "Command Palette", description: "Search for a command to run...",})const emits = defineEmits<DialogRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)</script>
<template> <Dialog v-slot="slotProps" v-bind="forwarded"> <DialogContent class="top-1/3 translate-y-0 overflow-hidden p-0 sm:max-w-xl" :show-close-button="false"> <DialogHeader class="sr-only"> <DialogTitle>{{ title }}</DialogTitle> <DialogDescription>{{ description }}</DialogDescription> </DialogHeader> <Command> <slot v-bind="slotProps" /> </Command> </DialogContent> </Dialog></template><script setup lang="ts">import type { PrimitiveProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { Primitive } from "reka-ui"import { computed } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { useCommand } from "."
const props = defineProps<PrimitiveProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
const { filterState } = useCommand()const isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,)</script>
<template> <Primitive v-if="isRender" data-slot="command-empty" v-bind="delegatedProps" :class="cn('py-6 text-center text-sm', props.class)" > <slot /> </Primitive></template><script setup lang="ts">import type { ListboxGroupProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { ListboxGroup, ListboxGroupLabel, useId } from "reka-ui"import { computed, onMounted, onUnmounted } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { provideCommandGroupContext, useCommand } from "."
const props = defineProps<ListboxGroupProps & { class?: HTMLAttributes["class"] heading?: string}>()
const delegatedProps = reactiveOmit(props, "class")
const { allGroups, filterState } = useCommand()const id = useId()
const isRender = computed(() => !filterState.search ? true : filterState.filtered.groups.has(id))
provideCommandGroupContext({ id })onMounted(() => { if (!allGroups.value.has(id)) allGroups.value.set(id, new Set())})onUnmounted(() => { allGroups.value.delete(id)})</script>
<template> <ListboxGroup v-bind="delegatedProps" :id="id" data-slot="command-group" :class="cn('overflow-hidden text-foreground', props.class)" :hidden="isRender ? undefined : true" > <ListboxGroupLabel v-if="heading" data-slot="command-group-heading" class="px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground"> {{ heading }} </ListboxGroupLabel> <slot /> </ListboxGroup></template><script setup lang="ts">import type { ListboxFilterProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { Search } from "@lucide/vue"import { reactiveOmit } from "@vueuse/core"import { ListboxFilter, useForwardProps } from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"import { useCommand } from "."
defineOptions({ inheritAttrs: false,})
const props = defineProps<ListboxFilterProps & { class?: HTMLAttributes["class"]}>()
const delegatedProps = reactiveOmit(props, "class")
const forwardedProps = useForwardProps(delegatedProps)
const { filterState } = useCommand()</script>
<template> <div data-slot="command-input-wrapper" class="flex h-12 items-center gap-2.5 border-b border-border px-3.5" > <Search class="size-4 shrink-0 text-muted-foreground" /> <ListboxFilter v-bind="{ ...forwardedProps, ...$attrs }" v-model="filterState.search" data-slot="command-input" auto-focus :class="cn('h-full w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)" /> </div></template><script setup lang="ts">import type { ListboxItemEmits, ListboxItemProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit, useCurrentElement } from "@vueuse/core"import { ListboxItem, useForwardPropsEmits, useId } from "reka-ui"import { computed, onMounted, onUnmounted, ref } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { useCommand, useCommandGroup } from "."
const props = defineProps<ListboxItemProps & { class?: HTMLAttributes["class"] }>()const emits = defineEmits<ListboxItemEmits>()
const delegatedProps = reactiveOmit(props, "class")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const id = useId()const { filterState, allItems, allGroups } = useCommand()const groupContext = useCommandGroup()
const isRender = computed(() => { if (!filterState.search) { return true } else { const filteredCurrentItem = filterState.filtered.items.get(id) // If the filtered items is undefined means not in the all times map yet // Do the first render to add into the map if (filteredCurrentItem === undefined) { return true }
// Check with filter return filteredCurrentItem > 0 }})
const itemRef = ref()const currentElement = useCurrentElement(itemRef)onMounted(() => { if (!(currentElement.value instanceof HTMLElement)) return
// textValue to perform filter allItems.value.set(id, currentElement.value.textContent ?? (props.value?.toString() ?? ""))
const groupId = groupContext?.id if (groupId) { if (!allGroups.value.has(groupId)) { allGroups.value.set(groupId, new Set([id])) } else { allGroups.value.get(groupId)?.add(id) } }})onUnmounted(() => { allItems.value.delete(id)})</script>
<template> <ListboxItem v-if="isRender" v-bind="forwarded" :id="id" ref="itemRef" data-slot="command-item" :class="cn(`group/command-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-45 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground`, props.class)" @select="() => { filterState.search = '' }" > <slot /> </ListboxItem></template><script setup lang="ts">import type { ListboxContentProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { ListboxContent, useForwardProps } from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<ListboxContentProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
const forwarded = useForwardProps(delegatedProps)</script>
<template> <ListboxContent data-slot="command-list" v-bind="forwarded" :class="cn('no-scrollbar max-h-80 scroll-py-1.5 overflow-x-hidden overflow-y-auto p-1.5 outline-none', props.class)" > <div role="presentation"> <slot /> </div> </ListboxContent></template><script setup lang="ts">import type { SeparatorProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { Separator } from "reka-ui"import { computed } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { useCommand } from "."
const props = defineProps<SeparatorProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
// Like cmdk: hide the separator while searching.const { filterState } = useCommand()const isRender = computed(() => !filterState.search)</script>
<template> <Separator v-if="isRender" data-slot="command-separator" v-bind="delegatedProps" :class="cn('-mx-1.5 my-1 h-px bg-border', props.class)" > <slot /> </Separator></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <span data-slot="command-shortcut" :class="cn('ml-auto font-mono text-[11.5px] tracking-wide text-muted-foreground', props.class)" > <slot /> </span></template>import { createContext } from "reka-ui";import type { Ref } from "vue";
export { default as Command } from "./Command.vue";export { default as CommandDialog } from "./CommandDialog.vue";export { default as CommandEmpty } from "./CommandEmpty.vue";export { default as CommandGroup } from "./CommandGroup.vue";export { default as CommandInput } from "./CommandInput.vue";export { default as CommandItem } from "./CommandItem.vue";export { default as CommandList } from "./CommandList.vue";export { default as CommandSeparator } from "./CommandSeparator.vue";export { default as CommandShortcut } from "./CommandShortcut.vue";
export const [useCommand, provideCommandContext] = createContext<{ allItems: Ref<Map<string, string>>; allGroups: Ref<Map<string, Set<string>>>; filterState: { search: string; filtered: { count: number; items: Map<string, number>; groups: Set<string>; }; };}>("Command");
export const [useCommandGroup, provideCommandGroupContext] = createContext<{ id?: string;}>("CommandGroup");<script lang="ts"> import * as Dialog from "$lib/registry/ui/dialog/index.js"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import Command from "./command.svelte"; import type { Command as CommandPrimitive, Dialog as DialogPrimitive } from "bits-ui"; import type { Snippet } from "svelte";
let { open = $bindable(false), ref = $bindable(null), value = $bindable(""), title = "Command Palette", description = "Search for a command to run...", showCloseButton = false, portalProps, children, class: className, ...restProps }: WithoutChildrenOrChild<DialogPrimitive.RootProps> & WithoutChildrenOrChild<CommandPrimitive.RootProps> & { portalProps?: DialogPrimitive.PortalProps; children: Snippet; title?: string; description?: string; showCloseButton?: boolean; class?: string; } = $props();</script>
<Dialog.Root bind:open {...restProps}> <Dialog.Header class="sr-only"> <Dialog.Title>{title}</Dialog.Title> <Dialog.Description>{description}</Dialog.Description> </Dialog.Header> <Dialog.Content class={cn("top-1/3 translate-y-0 overflow-hidden p-0 sm:max-w-xl", className)} {showCloseButton} {portalProps} > <Command {...restProps} bind:value bind:ref {children} /> </Dialog.Content></Dialog.Root><script lang="ts"> import { Command as CommandPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, ...restProps }: CommandPrimitive.EmptyProps = $props();</script>
<CommandPrimitive.Empty bind:ref data-slot="command-empty" class={cn( "py-6 text-center text-sm", className)} {...restProps}/><script lang="ts"> import { Command as CommandPrimitive, useId } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, children, heading, value, ...restProps }: CommandPrimitive.GroupProps & { heading?: string; } = $props();</script>
<CommandPrimitive.Group bind:ref data-slot="command-group" class={cn( "overflow-hidden text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:pt-1.5 **:[[cmdk-group-heading]]:pb-1 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-semibold **:[[cmdk-group-heading]]:text-muted-foreground", className)} value={value ?? heading ?? `----${useId()}`} {...restProps}> {#if heading} <CommandPrimitive.GroupHeading class="px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground"> {heading} </CommandPrimitive.GroupHeading> {/if} <CommandPrimitive.GroupItems {children} /></CommandPrimitive.Group><script lang="ts"> import { Command as CommandPrimitive } from "bits-ui"; import MagnifyingGlassIcon from "phosphor-svelte/lib/MagnifyingGlass"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, value = $bindable(""), ...restProps }: CommandPrimitive.InputProps = $props();</script>
<div data-slot="command-input-wrapper" class="flex h-12 items-center gap-2.5 border-b border-border px-3.5"> <MagnifyingGlassIcon class="size-4 shrink-0 text-muted-foreground" /> <CommandPrimitive.Input bind:ref bind:value data-slot="command-input" class={cn( "h-full w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", className )} {...restProps} /></div><script lang="ts"> import { Command as CommandPrimitive } from "bits-ui"; import CheckIcon from "phosphor-svelte/lib/Check"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, children, ...restProps }: CommandPrimitive.ItemProps = $props();</script>
<CommandPrimitive.Item bind:ref data-slot="command-item" class={cn( "group/command-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-45 data-[selected]:bg-accent data-[selected]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground", "data-disabled:pointer-events-none data-disabled:opacity-50", className )} {...restProps}> {@render children?.()} <CheckIcon class="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" /></CommandPrimitive.Item><script lang="ts"> import { Command as CommandPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, ...restProps }: CommandPrimitive.LinkItemProps = $props();</script>
<CommandPrimitive.LinkItem bind:ref data-slot="command-item" class={cn( "group/command-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-45 data-[selected]:bg-accent data-[selected]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground", "data-disabled:pointer-events-none data-disabled:opacity-50", className )} {...restProps}/><script lang="ts"> import { Command as CommandPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, ...restProps }: CommandPrimitive.ListProps = $props();</script>
<CommandPrimitive.List bind:ref data-slot="command-list" class={cn( "no-scrollbar max-h-80 scroll-py-1.5 overflow-x-hidden overflow-y-auto p-1.5 outline-none", className)} {...restProps}/><script lang="ts"> import { Command as CommandPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: CommandPrimitive.LoadingProps = $props();</script>
<CommandPrimitive.Loading bind:ref {...restProps} /><script lang="ts"> import { Command as CommandPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, ...restProps }: CommandPrimitive.SeparatorProps = $props();</script>
<CommandPrimitive.Separator bind:ref data-slot="command-separator" class={cn( "-mx-1.5 my-1 h-px bg-border", className)} {...restProps}/><script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();</script>
<span bind:this={ref} data-slot="command-shortcut" class={cn( "ml-auto font-mono text-[11.5px] tracking-wide text-muted-foreground", className)} {...restProps}> {@render children?.()}</span><script lang="ts"> import { Command as CommandPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
export type CommandRootApi = CommandPrimitive.Root;
let { api = $bindable(null), ref = $bindable(null), value = $bindable(""), class: className, ...restProps }: CommandPrimitive.RootProps & { api?: CommandRootApi | null; } = $props();</script>
<CommandPrimitive.Root bind:this={api} bind:value bind:ref data-slot="command" class={cn( "flex size-full flex-col overflow-hidden rounded-xl bg-popover text-popover-foreground", className)} {...restProps}/>import Root from "./command.svelte";import Dialog from "./command-dialog.svelte";import Empty from "./command-empty.svelte";import Group from "./command-group.svelte";import Input from "./command-input.svelte";import Item from "./command-item.svelte";import LinkItem from "./command-link-item.svelte";import List from "./command-list.svelte";import Loading from "./command-loading.svelte";import Separator from "./command-separator.svelte";import Shortcut from "./command-shortcut.svelte";
export { Dialog, Dialog as CommandDialog, Empty, Empty as CommandEmpty, Group, Group as CommandGroup, Input, Input as CommandInput, Item, Item as CommandItem, LinkItem, LinkItem as CommandLinkItem, List, List as CommandList, Loading, Loading as CommandLoading, Root, // Root as Command, Separator, Separator as CommandSeparator, Shortcut, Shortcut as CommandShortcut,};Built on cmdk.
| Prop | Type | Default |
|---|---|---|
CommandDialog open / onOpenChange |
boolean / (open) => void |
none |
CommandDialog title / description |
string |
screen-reader only |
CommandDialog showCloseButton |
boolean |
false |
CommandItem onSelect |
(value) => void |
none |
Parts: Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandShortcut, CommandSeparator.
✦ Edmi additions
Section titled “✦ Edmi additions”- The input is a flat search row with a bottom border; the selected item uses
--accent(cmdkdata-[selected=true]). - Shortcuts are mono text. The dialog uses the flat Edmi dialog surface.