Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Augani/kael/llms.txt
Use this file to discover all available pages before exploring further.
Kael renders overlays through a LayerStack that lives outside the normal element tree. This means a modal or popover paints on top of every other element in the window without needing z-index management in your layout. Each overlay element is controlled: you own the open/closed state in your view, pass it each frame, and respond to dismissal through an on_change callback.
modal()
modal() renders a full-screen backdrop and places your dialog content in the center of the window. It manages focus trapping, escape-key dismissal, and click-outside dismissal automatically.
Signature
pub fn modal(id: impl Into<ElementId>, open: bool) -> Modal
open drives visibility. When open is false, the modal and its backdrop are not rendered.
Methods on Modal
// Accessibility label for the dialog role
pub fn label(self, label: impl Into<SharedString>) -> Self
// Called with the requested new open state (false = dismiss)
pub fn on_change(
self,
listener: impl Fn(&bool, &mut Window, &mut App) + 'static,
) -> Self
// Supply caller-owned content for the dialog body
pub fn render_with(
self,
renderer: impl Fn(ModalRenderState, &Window, &App) -> AnyElement + 'static,
) -> Self
// Set the backdrop color (default: hsla(0, 0, 0, 0.32))
pub fn backdrop(self, color: Hsla) -> Self
// Control click-outside dismissal (default: true)
pub fn dismiss_on_click_outside(self, dismiss_on_click_outside: bool) -> Self
// Control escape-key dismissal (default: true)
pub fn dismiss_on_escape(self, dismiss_on_escape: bool) -> Self
ModalRenderState exposes open, label, and focused.
Dismissal flow
When the user clicks outside the dialog or presses Escape, the modal invokes your on_change callback with &false. Your view is responsible for setting the backing state variable to false and triggering a re-render. The modal does not close itself unilaterally.
Example: confirmation dialog
modal("delete-confirm", self.confirm_open)
.label("Confirm deletion")
.on_change(|&open, _window, cx| {
cx.update_view(self_entity, |view, _| view.confirm_open = open);
})
.render_with(|_state, _window, _cx| {
div()
.flex()
.flex_col()
.gap_4()
.p_8()
.bg(theme.surface)
.rounded_xl()
.shadow_lg()
.child("Are you sure you want to delete this item?")
.child(
div()
.flex()
.justify_end()
.gap_2()
.child(cancel_button)
.child(confirm_button),
)
.into_any_element()
})
You must call on_change and update your state variable to actually close the modal. If you omit on_change, the dismiss gestures have no effect and the modal will not close.
popover()
popover() renders an anchor element inline in your layout and floats a popup panel adjacent to it, positioned using the same LayerStack mechanism as modal(). Unlike modal(), there is no backdrop, and the popup anchors to the trigger’s measured bounds.
Signature
pub fn popover(id: impl Into<ElementId>, open: bool) -> Popover
Methods on Popover
// Called with the requested new open state
pub fn on_open_change(
self,
listener: impl Fn(&bool, &mut Window, &mut App) + 'static,
) -> Self
// Supply caller-owned content for the anchor (trigger) element
pub fn render_anchor_with(
self,
renderer: impl Fn(PopoverAnchorRenderState, &Window, &App) -> AnyElement + 'static,
) -> Self
// Supply caller-owned content for the popup panel
pub fn render_popup_with(
self,
renderer: impl Fn(PopoverPopupRenderState, &Window, &App) -> AnyElement + 'static,
) -> Self
// Control click-outside dismissal (default: true)
pub fn dismiss_on_click_outside(self, dismiss_on_click_outside: bool) -> Self
// Control escape-key dismissal (default: true)
pub fn dismiss_on_escape(self, dismiss_on_escape: bool) -> Self
// Offset the popup relative to the anchor bounds (default: 0, 4 px below)
pub fn offset(self, offset: Point<Pixels>) -> Self
PopoverAnchorRenderState exposes open.
PopoverPopupRenderState exposes open, width (matching the anchor), and anchor_bounds.
popover("options-menu", self.menu_open)
.on_open_change(|&open, _window, cx| {
cx.update_view(self_entity, |view, _| view.menu_open = open);
})
.render_anchor_with(|state, _window, _cx| {
div()
.child(if state.open { "▲ Options" } else { "▼ Options" })
.cursor_pointer()
.into_any_element()
})
.render_popup_with(|state, _window, _cx| {
div()
.w(state.width)
.flex()
.flex_col()
.bg(theme.surface)
.border_1()
.border_color(theme.border)
.rounded_md()
.shadow_md()
.child(menu_item("Rename"))
.child(menu_item("Duplicate"))
.child(menu_item("Delete"))
.into_any_element()
})
The popup appears 4 px below the anchor by default. Use .offset(point(px(0.0), px(8.0))) to increase the gap.
anchored()
anchored() is the low-level primitive that popover() and select() use internally. Use it directly when you need to position arbitrary content at a precise point in window coordinates while ensuring the content stays within the visible window bounds.
Signature
pub fn anchored() -> Anchored
Anchored takes no arguments. You configure the anchor point and children via builder methods.
Methods on Anchored
// Which corner of the child element aligns to the anchor position
// (default: Corner::TopLeft)
pub fn anchor(self, anchor: Corner) -> Self
// Anchor position in the configured coordinate system
pub fn position(self, anchor: Point<Pixels>) -> Self
// Fine-tune the final position with a pixel offset
pub fn offset(self, offset: Point<Pixels>) -> Self
// Interpret position as relative to the parent element instead of the window
pub fn position_mode(self, mode: AnchoredPositionMode) -> Self
// Flip to the opposite corner instead of overflowing the window edge
// (default behavior — AnchoredFitMode::SwitchAnchor)
// Snap to the window edge rather than switching corners
pub fn snap_to_window(self) -> Self
// Snap to the window edge with explicit margin insets
pub fn snap_to_window_with_margin(self, edges: impl Into<Edges<Pixels>>) -> Self
Anchored implements ParentElement, so you add children with .child() / .children().
Positioning modes
| Mode | Behavior |
|---|
AnchoredPositionMode::Window (default) | position() is relative to the window origin |
AnchoredPositionMode::Local | position() is relative to the parent element |
Fit modes
| Mode | Behavior |
|---|
AnchoredFitMode::SwitchAnchor (default) | Flip the anchor corner when content would overflow |
AnchoredFitMode::SnapToWindow | Keep the same corner but clamp to the window edge |
AnchoredFitMode::SnapToWindowWithMargin(edges) | Same as snap, with inset margins |
if let Some(position) = self.context_menu_position {
anchored()
.position(position)
.anchor(Corner::TopLeft)
.snap_to_window_with_margin(Edges::all(px(8.0)))
.child(
div()
.flex()
.flex_col()
.bg(theme.surface)
.border_1()
.border_color(theme.border)
.rounded_md()
.shadow_lg()
.child(context_menu_item("Open"))
.child(context_menu_item("Copy path"))
)
}
Anchored renders its children outside the normal flow at paint time. Do not place it inside a flex or grid container expecting it to participate in sibling layout — its position is always resolved relative to the window or parent origin, not the flex axis.
Layer stack and z-ordering
Both modal() and popover() maintain an internal LayerStack entity. Layers are painted in insertion order: the first modal opened sits below a modal opened later. Within a single popover, the popup always renders above the anchor. You do not need to assign z-indices manually; the layer system handles stacking.
When multiple modals or popovers are open at once, focus is restricted to the topmost layer. Pressing Escape dismisses layers from top to bottom, one at a time.