# iced_palette
> **Work in progress.** This crate is early-stage and not yet polished. APIs may change without notice between releases.
>
> **AI disclaimer.** Parts of this library and its documentation were written with the assistance of AI tools. Review the code before relying on it in production.
A command palette widget for [Iced](https://iced.rs) applications, with VS Code / Sublime Text-style fuzzy search.
`iced_palette` gives you a searchable, keyboard-navigable command overlay that you compose into your own `view`. It is generic over your application's `Message` type and styles itself from the active Iced theme, so it blends into your app out of the box.
## Features
- Fuzzy search with Sublime Text-style scoring (word-boundary, consecutive-match, and start-of-string bonuses) and live match highlighting.
- Keyboard navigation with wrapping, mouse hover selection, and click-outside-to-close.
- Optional per-command descriptions, categories, keyboard shortcuts (with platform-aware display), and search keywords.
- Submenus / nested command lists.
- Theme-aware styling via the host application's Iced theme.
- Two API styles: a builder-based **widget API** and a simpler **function API**.
## Installation
```toml
[dependencies]
iced_palette = "0.1"
iced = { version = "0.14", features = ["advanced"] }
```
## Quick start (widget API)
Keep a `PaletteState` in your application state and render `Palette` in your view while it is open.
```rust,ignore
use iced_palette::{Palette, PaletteState, command, Shortcut};
struct App {
palette: PaletteState,
}
#[derive(Debug, Clone)]
enum Message {
QueryChanged(String),
CommandSelected(&'static str),
PaletteClosed,
Save,
}
// Define your commands (typically built once):
let commands = vec![
command("save", "Save File")
.description("Save the current file")
.shortcut(Shortcut::cmd('s'))
.action(Message::Save),
];
// In your view, overlay the palette when open:
if self.palette.is_open() {
iced::widget::stack![
main_content,
Palette::new(&self.palette, &commands)
.on_query_change(Message::QueryChanged)
.on_select(Message::CommandSelected)
.on_close(|| Message::PaletteClosed),
]
.into()
} else {
main_content.into()
}
```
Opening the palette returns a `Task` that focuses the search input:
```rust,ignore
// In your update function:
Message::TogglePalette => self.palette.toggle(),
Message::QueryChanged(q) => { self.palette.set_query(q); Task::none() }
Message::PaletteClosed => { self.palette.close(); Task::none() }
```
`PaletteState` owns the open/closed flag, query, selection index, and submenu stack. Use `open`, `close`, `toggle`, `set_query`, `navigate_up`, `navigate_down`, `enter_submenu`, and `go_back` to drive it from your `update`.
## Function API
If you prefer to manage the palette state yourself, `command_palette` (and `command_palette_styled` for custom `PaletteConfig`) render the overlay directly from loose arguments. Note that here `on_select` yields the **display index** of the filtered list; map it back to the original command with `get_filtered_command_index`.
```rust,ignore
use iced_palette::{command_palette, get_filtered_command_index};
if palette_open {
iced::widget::stack![
main_view,
command_palette(
&query,
&commands,
selected_index,
Message::QueryChanged,
Message::Select, // receives the display index
Message::Navigate, // hover -> display index
|| Message::Cancel,
),
]
}
```
## Keyboard shortcuts and subscriptions
Iced subscription closures cannot capture state, so the keyboard subscription itself lives in your application. `iced_palette` provides the stateless building blocks:
- `is_toggle_shortcut(key, modifiers)` — matches Ctrl/Cmd+Space.
- `find_matching_shortcut(commands, key, modifiers)` — returns the matching command id (recurses into submenus).
- `collect_shortcuts(commands)` — all `(id, Shortcut)` pairs, including submenus.
- `navigate_up` / `navigate_down` — wrapping index math for arrow-key handling.
## License
MIT