Actions & Keybindings
Kael separates what happens (an action) from how it’s triggered (a keybinding or click). Actions are dispatched up the focused element tree, so a keystroke is routed to the nearest handler in the currently focused context — the same model that powers editor-grade keyboard UX.
Defining actions
The actions! macro generates zero-field action types in a namespace:
#![allow(unused)]
fn main() {
use kael::actions;
actions!(editor, [Save, Undo, Redo, Tab, TabPrev]);
}
Each entry becomes a type (Save, Undo, …) implementing the Action trait, with a stable name like editor::Save used for keymaps and dispatch.
Binding keys
Register bindings once at startup with cx.bind_keys. KeyBinding::new takes the keystroke string, the action, and an optional key context that scopes the binding:
#![allow(unused)]
fn main() {
use kael::{Application, App, KeyBinding};
Application::new().run(|cx: &mut App| {
cx.bind_keys([
KeyBinding::new("cmd-s", Save, None),
KeyBinding::new("cmd-z", Undo, None),
KeyBinding::new("cmd-shift-z", Redo, None),
KeyBinding::new("tab", Tab, Some("Editor")), // only in the "Editor" context
KeyBinding::new("shift-tab", TabPrev, Some("Editor")),
]);
// ... open windows ...
});
}
Keystroke syntax uses cmd / ctrl / alt / shift modifiers joined with -, and a space separates multi-key sequences (e.g. "cmd-k cmd-s"). Use cmd on macOS and ctrl on Windows/Linux.
Handling actions
In render, mark the element that owns a focus context with track_focus, then register handlers with on_action(cx.listener(...)). Handlers take &mut self, a reference to the action, the window, and the context:
#![allow(unused)]
fn main() {
use kael::{div, prelude::*, Context, FocusHandle, Render, Window};
struct Editor { focus_handle: FocusHandle }
impl Editor {
fn on_save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
// ... persist ...
cx.notify();
}
}
impl Render for Editor {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_save))
.on_action(cx.listener(Self::on_undo))
.child("editor surface")
}
}
}
Focus & tab order
Create focus handles from the context and arrange tab order with tab_index / tab_stop. Move focus from the window:
#![allow(unused)]
fn main() {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let items = vec![
cx.focus_handle().tab_index(1).tab_stop(true),
cx.focus_handle().tab_index(2).tab_stop(true),
cx.focus_handle().tab_index(3).tab_stop(true),
];
let focus_handle = cx.focus_handle();
window.focus(&focus_handle);
Self { focus_handle, items }
}
}
Window focus methods: window.focus(&handle), window.focus_next() (Tab), and window.focus_prev() (Shift-Tab). Query state with handle.is_focused(window) and style focused elements with .focus(|s| s.border_color(...)).
See examples/tab_stop.rs for a complete focus-navigation demo, and crates/kael/docs/key_dispatch.md for the dispatch internals.