Focus + Keyboard
Focus management and keyboard event dispatch are built into the tree's dispatch layer (wgpu-html-tree::dispatch).
Focus State
Focus is stored as a path in InteractionState::focus_path:
tree.interaction.focus_path = Some(vec![0, 2, 1]); // element at path [0,2,1] is focused
Setting Focus
// Programmatic focus
interactivity::focus(&mut tree, Some(vec![0, 1]));
// Clear focus
interactivity::blur(&mut tree);
// Tab to next focusable
interactivity::focus_next(&mut tree, false); // Tab
interactivity::focus_next(&mut tree, true); // Shift+Tab (reverse)
Focus is also set automatically on mouse down (primary button): the deepest focusable ancestor of the hit path receives focus, or focus is cleared if no ancestor is focusable.
Focusable Predicate
An element is keyboard-focusable if:
fn is_keyboard_focusable(element: &Element) -> bool {
matches!(element,
Element::Button(_) |
Element::Input(input) if !matches!(input.type_, "hidden") |
Element::TextArea(_) |
Element::Select(_) |
Element::Details { summary: _, open: _ } |
_ if element.attr("tabindex")
.and_then(|t| t.parse::<i32>().ok())
.is_some_and(|t| t >= 0)
)
}
Also focusable via tabindex:
tabindex="0": focusable in DOM ordertabindex="-1": focusable programmatically, not by Tabtabindex> 0: focusable with priority (higher values first)
Additionally, <a> elements with an href attribute are focusable.
Tab / Shift+Tab Navigation
Built into key_down():
// Tab key → focus next
if key == "Tab" && !modifiers.shift {
focus_next(tree, false);
}
// Shift+Tab → focus previous
if key == "Tab" && modifiers.shift {
focus_next(tree, true);
}
The order is depth-first DOM traversal. tabindex values order elements before DOM order.
Modifier State
Modifiers are tracked as a bitmask:
pub struct Modifiers {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub meta: bool,
}
Hosts update modifiers before dispatching keyboard events:
tree.set_modifier(Modifier::Shift, true); // Shift pressed
tree.set_modifier(Modifier::Ctrl, false); // Ctrl released
Modifier state is read during dispatch (e.g., Ctrl+C copy check).
Keyboard Event Dispatch
// In your event loop:
interactivity::key_down(&mut tree, &key_event, modifiers);
interactivity::key_up(&mut tree, &key_event, modifiers);
key_down handles:
- Esc → blur focus
- Tab / Shift+Tab → focus navigation
- Ctrl+A → select all text
- Ctrl+C → copy selection to clipboard
- Text input forwarding to focused form controls
on_key_down/on_key_upcallback dispatch on the focused element and bubbling
Callback Dispatch
When a key event fires, the engine:
- Looks up
focus_pathto find the focused element. - Fires
on_key_down/on_key_upon that element. - Bubbles up to root, firing on each ancestor that has the callback.
The general on_event callback also fires for keyboard events through the event type system.