Skip to content

Overlays

An overlay is a floating surface painted on the root layer above the application: a contextual panel, a modal dialog, a tooltip, a notification or a side drawer. The catalog's overlays are controlled — the caller owns the open flag and the reaction to a dismissal request — and they share one card anatomy, one focus policy and one dismissal rule. Ebox owns placement, occlusion and the anchor contract; the components own the panel's presentation and the keyboard scope. Positioning, Layers and Overlays defines anchoring, root-layer coordinates and the canvas reservation an overlay needs, Focus the modal scope, UI Overview the controlled contract, and Component Styles and Themes the panel tokens.

Every overlay renders nothing while its :open is nil, and one shared card while it is true: a one-row header carrying the title under a hairline, a body column padded by one row and two columns, and an optional footer row aligned to the right. A modal surface adds a small x in the header — pointer-only, so keyboard focus still lands on the first body control — and a footer confirmation button where the component defines one. Header and panel paint with the panel token triple, so a Theme restyles every surface at once.

The panel is one root-layer Host with the dialog role when modal and group otherwise, a negative Tab index, and the component's :on-close installed as its dismissal handler. Placement is delegated: a surface with an anchor follows the Ebox anchor/placement contract, a side drawer fills one edge at full height, and a centered dialog sizes itself within the visible window and bounds its height so a short window clips its own body rather than the application. A modal surface paints a full-screen backdrop that dims the application underneath instead of covering it: covered text and images stay visible while covered controls lose their pointer and focus targets. With the backdrop omitted the panel is still modal in the keyboard sense and the application's surface keeps its full paint.

Focus follows the same rules for every overlay. Opening focuses the first eligible control inside the panel (or the panel itself when it has none); while it is open the panel is a modal key scope, so Tab, arrows and Escape stay within the surface and dismissal returns focus to the control that opened it. Nesting works: each surface records the focus route it was opened from, and a closing inner surface hands focus back along that chain, skipping targets that no longer exist.

Component: etaf-popover (&key open anchor placement width title on-close)

Section titled “Component: etaf-popover (&key open anchor placement width title on-close)”

Show controlled OPEN content on Ebox's root layer, anchored to a unique semantic ID.

Arguments. OPEN is a value, ref or computed. ANCHOR is a nonempty symbol or string naming the Host the panel attaches to. PLACEMENT defaults to bottom-start and follows the anchor contract of Positioning, Layers and Overlays. WIDTH defaults to (ch 32). TITLE is an optional string. ON-CLOSE is required and receives no arguments. The default and footer slots hold the caller's content.

Semantics. The popover is the general anchored surface; menus, tooltips and toasts are built on it. While open, the panel is a modal key scope closed by Escape, and the panel's own dismissal requests ON-CLOSE without changing the caller's state. The application root must reserve a canvas height for floating content — without it the panel has nowhere to paint — and the caller owns the open flag, so two popovers can be open independently.

Returns. A View.

Errors. error→ etaf-component-call-error when ON-CLOSE is not a function, ANCHOR is not a nonempty semantic ID, or TITLE is neither a string nor nil.

Example.

(etaf-popover :open open :anchor 'status-chip :title "Details"
:on-close (lambda () (setf open nil))
(text "All checks passed."))

See also. etaf-dialog, etaf-tooltip.

Component: etaf-dialog (&key open title width height compact close-label on-close backdrop)

Section titled “Component: etaf-dialog (&key open title width height compact close-label on-close backdrop)”

Show a controlled modal panel using the same floating surface as Popover.

Arguments. OPEN is a value, ref or computed. TITLE defaults to "Dialog". WIDTH defaults to (ch 48). HEIGHT is nil or (lh N) with positive N, bounding the panel to text rows within the visible window. COMPACT removes the body's vertical air so short forms stay dense. CLOSE-LABEL defaults to "Close"; nil omits the default footer button so the footer slot orders every action. BACKDROP defaults to t. ON-CLOSE is required.

Semantics. The dialog centers itself in the window that displays its buffer when that window is graphical, and it never moves the application's geometry or global focus policy. The header shows TITLE and a pointer-only x that requests ON-CLOSE, and Escape does the same, so a modal always has a keyboard exit. With BACKDROP (the default) the backdrop dims the root canvas while keeping the covered application, text and images included, visible; covered controls and their focus targets stay blocked. With BACKDROP nil no backdrop is painted at all and the caller owns any outside-click policy. A fixed HEIGHT needs the body rows plus five for the header, body air and footer; a too-short window clips the dialog body instead of overflowing the frame. Dialogs own no form data.

Returns. A View.

Errors. error→ etaf-component-call-error when ON-CLOSE is not a function, TITLE is not a string, HEIGHT is neither nil nor (lh POSITIVE-INTEGER), COMPACT is not a boolean, or CLOSE-LABEL is neither a string nor nil.

Example.

(etaf-dialog :open ask :title "Confirm" :width '(ch 40)
:on-close (lambda () (setf ask nil))
(text "Proceed?")
(slot :name 'footer
(etaf-button :label "Delete" :variant 'primary :on-press #'delete)))

See also. etaf-popover, etaf-confirm-dialog.

Component: etaf-tooltip (&key open anchor text placement on-close)

Section titled “Component: etaf-tooltip (&key open anchor text placement on-close)”

Show controlled short text near an anchor using Popover placement. The caller controls visibility; no second hover event system is introduced.

Arguments. OPEN is a value, ref or computed. ANCHOR is the semantic ID. TEXT is a string or reactive string. PLACEMENT defaults to top-start. ON-CLOSE is required.

Semantics. The tooltip is a small anchored popover carrying one label, so its dismissal, focus scope and layer behavior are exactly those of etaf-popover. Because visibility is controlled, an application decides when a hint is appropriate — a button's event callback, not an implicit hover timer, is the usual trigger.

Returns. A View.

Errors. error→ etaf-component-call-error when TEXT does not resolve to a string or ON-CLOSE is not a function.

Example. (etaf-tooltip :open hint :anchor 'copy-button :text "Copy" :on-close #'hide-hint)

Component: etaf-toast (&key open anchor message title variant on-close)

Section titled “Component: etaf-toast (&key open anchor message title variant on-close)”

Show a controlled dismissible anchored notification. The caller owns its lifecycle and message state.

Arguments. MESSAGE is a string or reactive string. TITLE defaults to "Notification". VARIANT selects the semantic status color and defaults to info. ANCHOR and ON-CLOSE follow etaf-popover; the width is (ch 42) and the placement top-end.

Semantics. The body shows a status badge carrying the capitalized variant name above the message, so a success, warning, danger or info notice reads with the same status colors as the rest of the catalog. The toast never expires by itself: the application decides when to clear OPEN and remove it.

Returns. A View.

Errors. error→ etaf-component-call-error when MESSAGE does not resolve to a string, TITLE is not a string, or ON-CLOSE is not a function.

Example.

(etaf-toast :open notice :anchor 'status-chip :message "Saved" :variant 'success
:on-close (lambda () (setf notice nil)))

Component: etaf-drawer (&key open title placement width modal on-close)

Section titled “Component: etaf-drawer (&key open title placement width modal on-close)”

Show a controlled left or right side surface with default and footer slots.

Arguments. OPEN is a value, ref or computed. PLACEMENT is left or right (default right). WIDTH defaults to (ch 42) and is constrained to the root canvas. MODAL defaults to t. TITLE defaults to "Drawer". ON-CLOSE is required.

Semantics. The drawer fills one edge of the canvas at full height, taking its width from the prop and leaving the rest of the application visible. Its focus, dismissal, return and disposal follow the Dialog contract: modal mode adds the backdrop and traps the keyboard scope, non-modal mode leaves the surrounding surface interactive while Escape still requests ON-CLOSE. Form data and the drawer's content remain caller-owned.

Returns. A View.

Errors. error→ etaf-component-call-error when PLACEMENT is neither left nor right, MODAL is not a boolean, TITLE is neither a string nor nil, or ON-CLOSE is not a function.

Example.

(etaf-drawer :open filters :title "Filters" :placement 'right
:on-close (lambda () (setf filters nil))
(etaf-checkbox :label "Only stale"))

Ask a nonempty string PROMPT with the native yes/no minibuffer from a Host callback. Return non-nil on confirmation and nil on no; change application state only after confirmation.

Semantics. Emacs owns the yes/no prompt and Ebox restores focus to the valid entry afterwards; C-g aborts the callback, as does an entry that is no longer valid. No surface content is edited. Compose it in a Menu's ON-SELECT or a Button's ON-PRESS.

Returns. Non-nil when confirmed, nil otherwise.

Errors. error→ etaf-event-error outside a Host event callback; error→ etaf-view-error when PROMPT is not a nonempty string.

Example. (etaf-confirm-dialog "Delete this file?")

See also. etaf-dialog.

Function: etaf-ui-dismiss-outside-press ()

Section titled “Function: etaf-ui-dismiss-outside-press ()”

Dismiss every mounted floating surface in the current event's Runtime whose panel does not contain the pressed Host, requesting each one's ON-CLOSE, and return the number dismissed.

Semantics. Call it from the :on-press or :on-press-capture callback of a Host enclosing the application, such as a shell root, to give unpainted (:backdrop nil) modals and popovers click-outside dismissal. Each dialog, launcher, switcher, popover, menu or drawer mounted in the Runtime whose panel does not contain the pressed Host receives a dismissal request; a surface opened by the press being handled is not mounted yet and stays open. The function requests closes through the normal event path, so a surface whose owner ignores the request simply stays open.

Returns. The number of surfaces dismissed.

Errors. error→ etaf-event-error outside a Host event callback.

Example.

(box :ref 'shell :on-press-capture #'etaf-ui-dismiss-outside-press
(etaf-popover :open open :anchor 'status-chip
:on-close (lambda () (setf open nil))
(text "Details")))

See also. etaf-popover, etaf-drawer.

  • error→ etaf-component-call-error reports every rejected prop through etaf-check-prop, naming the component, property, received value and expectation: a missing or non-function ON-CLOSE, a bad anchor, a malformed fixed height, a non-boolean flag or a non-string title.
  • Dismissal is a request, never a state change: Escape, the header x and an outside press all run ON-CLOSE, and the application decides whether the surface closes. A callback that ignores the request leaves the surface open.
  • A closing dialog or drawer restores focus to the control that opened it, or to the nearest still-valid target along the nesting chain; an unmounted return target is skipped rather than signaling.
  • A rejected or cancelled confirmation changes nothing: etaf-confirm-dialog returns nil on no and aborts the callback on C-g, so the caller's state stays as it was.

See also. UI Overview, Focus.

Runnable example

(etaf-view
(column :width '(ch 60) :height '(lh 18)
(box :id 'status-chip (text "Status: ready"))
(etaf-popover :open t :anchor 'status-chip :title "Details" :on-close #'ignore
(column (text "All checks passed.")))))