Customization
Customization
Cmd+kit is configured in code. The main customization surface is the command structure itself, followed by messages, theme tokens, render overrides, and optional async sources.
If you want a guided UI for exploring these settings before coding them, see the playground documentation.
Define sections and items
const sections = [
{
id: "navigation",
title: "Navigation",
items: [
{
id: "dashboard",
title: "Dashboard",
subtitle: "Open the main workspace",
href: "/dashboard",
keywords: ["home", "workspace"]
}
]
}
]; Nested navigation
const sections = [
{
id: "search",
title: "Search",
items: [
{
id: "docs",
title: "Documentation",
children: [
{
id: "guides",
title: "Guides",
items: [
{ id: "api", title: "API reference" }
]
}
]
}
]
}
]; Theme tokens
<CommandPalette
sections={sections}
theme={{
light: {
accentColor: "#0fa6d8",
backgroundColor: "#ffffff",
textColor: "#0e1720"
},
dark: {
accentColor: "#12b5e5",
backgroundColor: "#0f1720",
textColor: "#f5fbff",
mutedColor: "#9fb4c4",
borderColor: "#264152",
overlayColor: "rgba(4, 9, 13, 0.64)",
radius: "22px",
shadow: "0 24px 80px rgba(0, 0, 0, 0.42)"
}
}}
/> Icons
Icons are usually customized in the render layer. In React and Preact use renderItem or renderers.item. In Vue use the item slot.
renderItem={(item, active) => (
<div className={active ? "palette-row is-active" : "palette-row"}>
<MyIcon name={item.id} />
<span>{item.title}</span>
</div>
)} <template #item="{ item, active }">
<div :class="['palette-row', { 'is-active': active }]">
<MyIcon :name="item.id" />
<span>{{ item.title }}</span>
</div>
</template> Messages
<CommandPalette
sections={sections}
messages={{
searchPlaceholder: "Search commands",
noResults: "No results match your query.",
closeLabel: "Close palette"
}}
/> Render and style overrides
<CommandPalette
classNames={{
dialog: "palette-shell",
item: "palette-item",
emptyState: "palette-empty"
}}
renderers={{
title: ({ activeTitle, breadcrumbs }) => (
<span>{breadcrumbs.join(" / ") || activeTitle}</span>
),
emptyState: ({ query }) => <span>No result for "{query}"</span>
}}
sections={sections}
/> Async source
<CommandPalette
source={async () => {
const response = await fetch("/api/commands");
return response.json();
}}
title="Workspace commands"
/> Generate CSS variables
import { createThemeCssText } from "@cmd-kit/core";
const themes = {
light: { accentColor: "#0fa6d8", backgroundColor: "#ffffff" },
dark: { accentColor: "#12b5e5", backgroundColor: "#0f1720" }
};
const darkCss = createThemeCssText(themes.dark);
const lightCss = createThemeCssText(themes.light);
const themeBlock = `:root {
${darkCss}
}
html[data-theme="light"] {
${lightCss}
}`; FAQ
Should icon components live in the command data model?
Usually no. Keep data framework-agnostic and map icons in renderers/slots.
What is the cleanest styling strategy?
Use theme for shared design tokens and classNames for slot-level CSS control.
Can I use both nested navigation and async source data?
Yes, as long as async payloads keep the same section/item shape including nested children.
How should I localize placeholder and empty-state text?
Override messages from your app-level i18n layer instead of hard-coding strings in command data.
How do I keep custom renderers maintainable?
Keep renderer functions focused on presentation and avoid embedding business logic that belongs in command generation.