Anchored overlays
An anchored overlay is any surface whose position is derived from something else on screen: a tooltip beside a button, a menu under a menubar item, a submenu beside its parent row, a completion list next to a caret, a hover card beside a link, a teaching tip beside a feature. Thirty-eight systems were read at pinned revisions to answer one question — how much of that problem is surface-independent? — because sparkles:ui must solve it once, in integer cells, for a GPU window, a terminal cell grid, a script-free HTML page and an Android NativeActivity, none of which agree on whether hover exists, whether a key release arrives, whether there is a frame clock, or whether there is an OS window at all.
The corpus was chosen to make the surface question falsifiable rather than assumed. It spans a wire protocol whose placement runs in another process (xdg_positioner), a browser engine (Blink), ten desktop toolkits, three mobile stacks, ten headless web libraries, and ten terminal systems that have no compositor, no hover and no timers — plus the one historical subject (Turbo Vision) that already built the whole vocabulary inside a character grid in the early 1990s. Where the field's answers diverge, the divergence is reported as a fork rather than resolved by preference; where a first-pass claim did not survive verification, the narrower wording is what appears in the tree.
This survey answers ten questions
- What is the minimal surface-independent core? — six values and pure functions over them, with a short, explicit list of what is irreducibly surface-specific. Vocabulary in
concepts.md. - Can placement be a pure function of Regular values? — yes, with two amendments the verification pass forced; demonstrated by
place-reference.d. - What does the cell grid actually cost? — which behaviours degrade gracefully, which degrade badly, and which are simply unrepresentable in whole cells. Per-target constraints in
sparkles-baseline.md. - What replaces hover? — on targets with no pointer, and on targets where hover exists but warm-up has no clock. Timing behaviours catalogued in
features-people-forget.md; the machine istooltip-timing.d. - What does a script-free HTML target get? — what survives when the only runtime is the cascade, read against CSS anchor positioning and the Popover API.
- How much of Floating UI will the browser absorb? — which of the middleware set the platform has already taken, and which parts it structurally cannot.
- Overlay tree, or overlay list? — why the tree must be a query over a flat ordered list, not a structure. Layering vocabulary in
concepts.md. - Where does adaptive presentation belong? — popover-versus-sheet-versus-dialog as a host decision above the primitive, on Apple's and WinUI's evidence.
- What must the API not foreclose? — the decisions that are cheap now and unaffordable later, which is what
proposal.mdis built around. - What should Sparkles build? — the final synthesis, taken up as a milestoned plan in
proposal.mdagainst the delta table insparkles-baseline.md.
Last reviewed: August 14, 2026
IMPORTANT
Every claim in this tree is tied to a primary source at a pinned revision, recorded in the revision ledger below. A citation is the upstream URL at that SHA — so any statement here can be re-checked against exactly the code that produced it, years from now. Load-bearing statements in comparison.md additionally passed a two-lens adversarial verification pass; statements that are analysis rather than observation are marked INFERENCE there.
Master catalog
Thirty-eight subjects. Surface model is the single most consequential column for this toolkit: OS popup means the overlay is a separate window the compositor or window manager owns; in-canvas means it is painted into the same surface as everything else — the constraint sparkles:ui works under; both means one declaration resolves to either at runtime, which is the interesting case, because it proves the two share a core.
| Subject | Category | Where the behaviour lives | Surface model | Deep-dive |
|---|---|---|---|---|
| Floating UI | Web / headless positioning | A ~100-line kernel folding an ordered middleware array over one tuple, behind a three-method Platform | in-canvas (coordinates only; the top layer is detected, never entered) | floating-ui.md |
| Radix Primitives | Web / headless behaviour | Four separately-consumable mechanisms: Popper, DismissableLayer, FocusScope, Portal | in-canvas (React portal into document.body; no top layer) | radix.md |
| Base UI | Web / headless behaviour | One ~800-line positioning hook plus one reason-tagged popup store shared by ten components | in-canvas (DOM only; no showPopover, no dialog) | base-ui.md |
| Ariakit | Web / headless behaviour | A single-inheritance chain of stores and prop-transformer hooks; a 96-line polygon test in-house | in-canvas (React portal into document.body) | ariakit.md |
| Zag.js | Web / state machines | A statechart as inert data plus one single-purpose package per concern (popper, dismissable) | in-canvas (content portalled to document.body) | zag.md |
| Headless UI | Web / headless behaviour | A pure-reducer Machine plus a forty-line stack machine; no positioner and no arrow, deliberately | in-canvas (one userland portal root, an explicitly non-native "top layer") | headlessui.md |
| Floating Vue | Web / framework binding | One 1187-line Popper.ts; tooltip, dropdown and menu are nine-line files differing by a theme string | in-canvas (one container, one flat z-index: 10000) | floating-vue.md |
| Tippy.js | Web / imperative controller | 1145 lines of when — timing, intent and lifecycle — configuring Popper 2 for the where | in-canvas (a div in the same document; z-index: 9999) | tippy.md |
| Angular CDK Overlay | Web / enterprise manager | OverlayRef + FlexibleConnectedPositionStrategy + a scroll strategy + one document-level key/pointer stack | both — legacy .cdk-overlay-container div; since v22 the browser top layer by feature test | angular-cdk.md |
| React Aria | Web / headless behaviour | A hand-written ~850-line calculatePosition plus a document-global tooltip warm-up machine | in-canvas (DOM portal into document.body) | react-aria.md |
| HTML Popover API | Web platform | Spec algorithms for stacking, cascade dismissal, focus scope and re-entrancy — and no geometry at all | both — the top layer is an in-document ordered set; the one OS surface is select's native picker | popover-api.md |
| CSS Anchor Positioning | Web platform | The cascade: anchor(), anchor-name, position-try-fallbacks, and an incumbent-skip hysteresis rule | in-canvas (the UA top layer is a paint-order set, not an OS popup) | css-anchor.md |
| Chromium / Blink | Web platform (impl) | Two systems sharing one pointer: popover owns lifecycle, anchor positioning owns geometry | in-canvas (Document::top_layer_elements_, a per-document vector) | blink.md |
| WAI-ARIA APG | Web platform / a11y | Patterns and six reference implementations: roles, focus ownership, dismissal, modality — no placement | in-canvas (position: absolute inside the trigger's containing block) | aria-apg.md |
| Qt Quick Controls | Native desktop (Qt) | One QQuickPopup base class: a declaration plus a permission set (ClosePolicy, six placement booleans) | both | qt-quick-controls.md |
| Qt Widgets | Native desktop (Qt) | One window flag (Qt::Popup) buying the stack, the grab, event re-routing and modality exemption | OS popup | qt-widgets.md |
| GTK4 | Native desktop (GTK) | GdkPopupLayout — a ten-scalar value — plus a swappable solver (compositor, GDK arithmetic, or Android) | both — a compositor surface everywhere except the Android backend, which clips to the parent | gtk4.md |
| WPF | Native desktop (Windows) | Candidate generation plus scoring in PlacementMode/CustomPopupPlacement, one HwndSource per popup | OS popup (WS_POPUP + WS_EX_TOPMOST + WS_EX_NOACTIVATE) | wpf.md |
| WinUI | Native desktop (Windows) | Two stacks sharing a portal and a dismissal layer: CPopup/FlyoutBase, plus TeachingTip's forked 14-candidate solver | both | winui.md |
| Uno Platform | Native desktop/web (.NET) | Every open popup is a full-window Panel under one in-app PopupRoot; placement is managed Rect arithmetic | in-canvas (plus one Android full-screen native window contributing no placement) | uno.md |
| Avalonia | Native desktop (.NET) | ManagedPopupPositioner — a ~130-line solver modelled on xdg_positioner — behind two adapters | both — the same solver serves a PopupRoot window and an in-window OverlayPopupHost | avalonia.md |
| Slint | Native desktop (Rust) | One point anchor, one clamp-and-shrink pass, a flat stack of extra item trees; policy pushed into .slint | both — an in-window ChildWindow, a real OS child window, or a native OS menu | slint.md |
| Zed / GPUI | Native desktop (Rust GPU) | A flat per-frame vector of deferred paint records; anchored() elements; no top layer, no grab | in-canvas (one OS window, one GPU surface) | gpui.md |
| Dear ImGui | Immediate-mode GUI | Two flat POD arrays, one ~70-line integer placement function, one draw-layer bit | in-canvas (every popup is an ordinary ImGuiWindow in the same ImDrawData) | imgui.md |
| Jetpack Compose | Mobile / adaptive (Android) | PopupPositionProvider.calculatePosition(anchorBounds, windowSize, layoutDirection, contentSize) → IntOffset | OS popup (a real WindowManager child window per popup) | compose.md |
| Flutter | Mobile / adaptive | OverlayPortal / RawMenuAnchor / PopupRoute in one render tree; positionDependentBox for tooltips | in-canvas (one render tree, one surface; SystemContextMenu on iOS is the one escape hatch) | flutter.md |
| Apple (UIKit/AppKit/SwiftUI/TipKit) | Mobile & desktop | Nine overlapping primitives across four frameworks, sharing a content model but never an anchor or a policy | both — NSPopover/NSMenu are OS surfaces; UIPopoverPresentationController is an in-window presentation | apple.md |
Wayland xdg_positioner | Protocol-level algebra | A ~40-byte POD of plain ints solved out of process by the compositor | OS popup (a compositor-managed wl_surface with the xdg_popup role) | xdg-positioner.md |
| Helix | Terminal / cell grid | One ~60-line integer-cell function re-run from scratch every frame; a flat Vec of self-clipping painters | in-canvas | helix.md |
| Neovim floats | Terminal / cell grid | nvim_open_win: an anchor descriptor resolved to one cell, clamped; ui_compositor merges by zindex | both — a float is an in-canvas cell rectangle, but opaque to the plugin that opened it | neovim-floats.md |
| Notcurses | Terminal / cell grid | The ncplane — an independently owned cell buffer in a totally ordered ncpile | in-canvas (one framebuffer; the terminal is the only surface) | notcurses.md |
| nui.nvim | Terminal / cell grid | A declarative geometry vocabulary — {relative, position, size, anchor, border} — normalised by one function | in-canvas (Neovim floats; no collision engine at all) | nui.md |
| nvim-cmp / blink.cmp | Terminal / cell grid | Menu-beside-a-text-range, then docs-beside-the-menu, on a pure integer grid with no pointer | both — floats are in-canvas cells, but opaque rect+zindex handles to the plugin | nvim-completion.md |
| Ratatui | Terminal / cell grid | No overlay primitive: a ten-line Clear widget, a Rect with clamp/intersection, and later-call-wins | in-canvas | ratatui.md |
| Textual | Terminal / cell grid | Three CSS rules taught to the ordinary layout engine — layers/layer, overlay, constrain | in-canvas | textual.md |
| tmux | Terminal / cell grid | display-popup / display-menu painted into the grid tmux already owns; three generations side by side | in-canvas | tmux-popup.md |
| Turbo Vision | Terminal / cell grid (historical) | A complete windowing system in TScreenCells: TView/TGroup, menus, submenus, modals, drop shadows | in-canvas (the console is the only surface) | turbo-vision.md |
| Emacs posframe + company | Terminal / GUI hybrid | A poshandler: a named pure function of a flat measured record; plus a second, surface-free cell renderer | both — a child frame in GUI Emacs, an overlay-based pseudo-tooltip in a terminal | emacs-posframe.md |
Beyond the deep-dives, five synthesis documents:
| Document | What it is |
|---|---|
concepts.md | The shared vocabulary — anchor, placement, constraint adjustment, boundary, top layer, warm-up, safe polygon, grab, modality, geometry metadata — each defined once, with canonical spellings and an index of which subject exercises it |
comparison.md | The capstone: twelve capabilities × thirty-eight subjects, sixteen dimensions with a best-in-class per dimension, the consensus, the genuine forks, and the ten questions |
features-people-forget.md | The behaviours nobody remembers until they ship — anchoring, collision, timing, pointer intent, dismissal, focus, layering, accessibility, animation, degradation |
sparkles-baseline.md | What sparkles:ui can express today: WidgetKind.popup, the Palette popup metrics, clampOrigin, HoverPopup, DCK5, the open defects, the per-target constraint list, and the delta table |
proposal.md | The milestoned plan: the primitive architecture, the bands-versus-precedence resolution, an API straw man, the state machines, the geometry pipeline, the adapter surface, and the open questions |
And three runnable examples that CI compiles and runs, so the conclusions cannot rot:
| Example | The claim it pins |
|---|---|
place-reference.d | place() as a @safe pure nothrow @nogc function over Regular values returning a decision record, not a point |
tooltip-timing.d | The warm-up / cool-down machine as one shared arbiter holding two integers, composed from the existing Timeline |
dismissal-policy.d | Dismissal as one value: a policy flags word ANDed with a router-offered cause |
Revision ledger
Every subject, the upstream repository, and the exact revision it was read at. This is what makes the tree re-verifiable: a claim on any page can be checked against precisely this code. SHAs are given in full and are never abbreviated.
| Subject | Repository | Revision read | Date read |
|---|---|---|---|
| Dear ImGui | ocornut/imgui | 46d39d56febc2a00bdd2270dc88c8a13f2a0441a | 2026-08-11 |
| Apple (UIKit/AppKit/SwiftUI/TipKit) | developer.apple.com | docs-only — no public source (reference pages as published August 2026) | 2026-08-11 |
| Jetpack Compose | androidx/androidx | 268d841a45644cadf438fc335c793869728449ec | 2026-08-11 |
| Flutter | flutter/flutter | feab40b83b8d1954106e83bb1d7b52265a41cb45 | 2026-08-11 |
| Avalonia | AvaloniaUI/Avalonia | aee3f68551b0ac4417e32996a6627f34462edbc3 | 2026-08-11 |
| GTK4 | GNOME/gtk | 817caae3dd5bc8ff6f4a96d5bef0aa0dc0bec671 (GTK 4.23.1) | 2026-08-11 |
| Qt Quick Controls | qt/qtdeclarative | ffc46f28ab21b6666dbea46c81cf2726ce682419 | 2026-08-11 |
| Qt Widgets | qt/qtbase | d0787745aa43e5baf49de876f917946df6aceca5 | 2026-08-11 |
| Zed / GPUI | zed-industries/zed | d71f1461045c098dc6ca6b1b5adcf1b8949722e8 | 2026-08-11 |
| Slint | slint-ui/slint | 24318cebc2b3feed4f7187e237915f52715ce285 | 2026-08-11 |
| WPF | dotnet/wpf | 99caccf23145777f910711b51961885bec783213 | 2026-08-11 |
| WinUI | microsoft/microsoft-ui-xaml | 29ebf098f70df518b57b754130bc94004be8c6bc (winui3/main) | 2026-08-11 |
| Uno Platform | unoplatform/uno | df5d18a850248cb8c2ccb34032b4ebeb54dc8283 | 2026-08-11 |
Wayland xdg_positioner | wayland/wayland-protocols | afb614d5fcbd02d261a6ae91920aa91cf3915a8a (wayland-protocols 1.49) | 2026-08-11 |
| Emacs posframe + company | tumashu/posframe + company-mode/company-mode | posframe 74c8c56131ed866db47ae4191364b72dd4852456 (v1.5.2, 2026-05-27); company-mode 1cc907ac9e46ae4209eb5a341131787e0c678406 (1.1.0, 2026-07-21) | 2026-08-11 |
| Helix | helix-editor/helix | 14d6bc0febed9c692048271a8ae2362ac969c6e0 | 2026-08-11 |
| Neovim floats | neovim/neovim | 2757f6eef92a99812d5ad12408d03592bd54f10c | 2026-08-11 |
| Notcurses | dankamongmen/notcurses | b26048eebc74d5d254717d3332fa484718f9efe6 | 2026-08-11 |
| Ratatui | ratatui/ratatui | a2ca2df5688772baffb743b494761f4ec82b3174 | 2026-08-11 |
| Textual | Textualize/textual | 06dbeef4bb70fb718236aa418ed658ef4667a126 | 2026-08-11 |
| nui.nvim | MunifTanjim/nui.nvim | de740991c12411b663994b2860f1a4fd0937c130 | 2026-08-11 |
| nvim-cmp / blink.cmp | Saghen/blink.cmp + hrsh7th/nvim-cmp | blink.cmp 8ca29c2eb34f5ce4770bffc0d62e6f636a4e8526 (2026-08-08); nvim-cmp 2ffe79f1f021def8dd1fcd81deb16f1bb0d989f3 (2026-07-10); corroborated against neovim 2757f6eef92a99812d5ad12408d03592bd54f10c | 2026-08-11 |
| tmux | tmux/tmux | 851c5a933d4838c32ad06c248b2ba975d106149c (next-3.8) | 2026-08-11 |
| Turbo Vision | magiblot/tvision | 57b6f56b38e0ee75240a80a10ee0e11470c24693 | 2026-08-11 |
| Angular CDK Overlay | angular/components | f3e6276c969f33e527b616ef8bf7b0404685721d | 2026-08-11 |
| Floating Vue | Akryum/floating-vue | 19857764c4f73dea7ed44a7d970adb968ee7ad90 | 2026-08-11 |
| Ariakit | ariakit/ariakit | a0426ed547d95b84c9d53033053e51baeaca4aaa | 2026-08-11 |
| Base UI | mui/base-ui | adbd590484b26c1e68049348c57c70998ad667a7 | 2026-08-11 |
| Headless UI | tailwindlabs/headlessui | eea57cf46fd6767ed1059012f7073b88eb159fba (@headlessui/react 2.2.10) | 2026-08-11 |
| Radix Primitives | radix-ui/primitives | f7ecd5ab16f5e1e820eb5786a1419a98a2d594ae | 2026-08-11 |
| React Aria | adobe/react-spectrum | 7c0765468a1d161ab9ac88ca9f1b54d3603a275c | 2026-08-11 |
| Floating UI | floating-ui/floating-ui | 0eb8c985a6d7aadc3fbe621acfc3d7f1cdd6fdf1 (@floating-ui/dom 1.8.0) | 2026-08-11 |
| Tippy.js | atomiks/tippyjs | ad85f6feb79cf6c5853c43bf1b2a50c4fa98e7a1 (v6.3.7) | 2026-08-11 |
| Zag.js | chakra-ui/zag | eabc04440baa219723bc5d9a51d4e95c1deaf024 | 2026-08-11 |
| CSS Anchor Positioning | w3c/csswg-drafts | 6dc15cc9cb15043840eacf081e89f5a666fa7889 | 2026-08-11 |
| HTML Popover API | whatwg/html | ac0389a3aca0331055bf4bf23f509c2913e3f795 | 2026-08-11 |
| Chromium / Blink | chromium/src | b0e30a9973232cee28901ea5d6cd4de6ea9428aa | 2026-08-11 |
| WAI-ARIA APG | w3c/aria-practices | 7e4034b262bc0d25332e330d8a582aaf34113829 | 2026-08-11 |
NOTE
Two subjects are specification readings rather than implementation readings (xdg_positioner and CSS Anchor Positioning), and one (Apple) is documentation-only because no source is published. Each page says so in its own header and marks every claim accordingly. Blink was read through a sparse checkout, so its legacy select popup-menu path is outside the reading.
Taxonomy
The same thirty-eight subjects, re-cut one axis at a time.
By surface model
The axis that decides how much of the field transfers to a single-surface toolkit. The both row is the load-bearing one: eleven subjects run the same placement code on an OS surface and on an in-window one, which is the strongest available evidence that placement is surface-independent.
| Surface model | Subjects | Count |
|---|---|---|
| OS popup — a compositor/WM-owned surface | Compose, Qt Widgets, WPF, xdg_positioner | 4 |
| Both — one declaration, either surface at runtime | Angular CDK, Apple, Avalonia, GTK4, Neovim, nvim-cmp / blink.cmp, posframe + company, Popover API, Qt Quick Controls, Slint, WinUI | 11 |
| In-canvas — painted into the surface it shares | Ariakit, Base UI, Blink, CSS anchor, Dear ImGui, Floating UI, Floating Vue, Flutter, GPUI, Headless UI, Helix, Notcurses, nui.nvim, Radix, Ratatui, React Aria, Textual, Tippy, tmux, Turbo Vision, Uno, WAI-ARIA APG, Zag | 23 |
By placement algorithm family
Six families of candidate generation. Per comparison.md § 2, every one of them is integer arithmetic over four rects and a small policy value — the families differ on how candidates are produced, not on what they compute.
| Family | Subjects | Count |
|---|---|---|
| Per-axis adjustment bits — a flags mask; no candidate combinatorics | xdg_positioner, GTK4, Avalonia, Qt Quick Controls, Slint, Textual | 6 |
| Ordered candidate list — a fallback array, first fit wins | Floating UI, Radix, Base UI, Ariakit, Zag, Floating Vue, Tippy, Headless UI (delegated), CSS anchor, Blink, WinUI, Uno, Compose, Dear ImGui | 14 |
| Scored candidates — a real objective (visible area), with a total order | Angular CDK, WPF | 2 |
| Single-candidate flip-then-clamp — exactly one alternative, then clamp | Qt Widgets, React Aria | 2 |
| Free-space budget — pick a side by room, hand that room to layout as a budget | Helix, Neovim, nvim-cmp / blink.cmp, GPUI, Flutter, tmux | 6 |
| No solver — clamp-only, caller-supplied, or deliberately out of scope | Apple (unpublished/system-owned), posframe + company (a caller-selected pure poshandler), Notcurses, Ratatui, nui.nvim, Turbo Vision, WAI-ARIA APG, Popover API | 8 |
By dismissal model
Who decides that a press landed "outside", and who owns the cause vocabulary. Values follow the comparison.md at-a-glance matrix; the mechanics are in comparison.md § 8 and concepts.md § 8.
| Dismissal model | Subjects | Count |
|---|---|---|
| Substrate-delivered — the grab, the compositor or the UA decides | GTK4, Popover API, Blink, Apple, xdg_positioner | 5 |
| Library-owned and on by default | Radix, Base UI, Ariakit, Headless UI, Floating Vue, Tippy, React Aria, Qt Quick Controls, Qt Widgets, WPF, WinUI, Uno, Avalonia, Slint, GPUI, Dear ImGui, Compose, Flutter, Textual, tmux, Turbo Vision, posframe + company | 22 |
| Opt-in package | Floating UI (@floating-ui/react's useDismiss) | 1 |
| Partial — present but restricted or divergent between paths | Zag, WAI-ARIA APG, Helix, Notcurses | 4 |
| Caller's problem — the seam is exposed and nothing is decided | Angular CDK, Neovim, nui.nvim, Ratatui | 4 |
| Absent | CSS anchor (out of scope by design), nvim-cmp / blink.cmp | 2 |
NOTE
The single best-shaped answer in the corpus is Qt Quick Controls' ClosePolicy plus tryClose(pos, phase) — a flags word ANDed with a cause the router supplies in the same vocabulary the policy is written in. Uno and WinUI reached the same shape independently and it rotted into verified dead code for want of a caller. dismissal-policy.d is that design in D.
By state architecture
Per comparison.md § 15, only four of thirty-eight model the open/close lifecycle as an explicit machine. Rows 3 and 4 are techniques and can co-occur with rows 1–2; row 5 is the residual.
| State architecture | Subjects | Count |
|---|---|---|
| Explicit lifecycle machine | Zag (declarative statechart), Headless UI (pure reducer, total dispatch), GPUI (tooltip enum), Compose (sealed context-menu status) | 4 |
| Mount / transition lifecycle only | Radix (three-state presence), Qt Quick Controls (transition state), Base UI (transition status) | 3 |
| Openness as membership in one ordered array — no per-overlay boolean | Dear ImGui (compare two stack lengths), Popover API + Blink (the popover stack), Slint (monotonically-keyed vector), Headless UI (forty-line stack machine) | 5 |
| Transactional value update — copy, modify, replace; restore on validation failure | Neovim (key-presence-guarded partial config patches), xdg_positioner (normatively pinned copy-on-use), tmux (preferred beside current geometry) | 3 |
| Loose booleans over an imperative controller — the rest of the catalog | Angular CDK, Apple, Ariakit, WAI-ARIA APG, Avalonia, CSS anchor, Floating UI, Floating Vue, Flutter, GTK4, Helix, Notcurses, nui.nvim, nvim-cmp / blink.cmp, posframe + company, Qt Widgets, Ratatui, React Aria, Textual, Tippy, Turbo Vision, Uno, WinUI, WPF | 24 |
By whether the overlay can escape its parent's bounds
The unclippability question. It is what OS popups are bought for — and the third row is the proof that a survey of "how do I escape a clip" is not the same survey as "where does the box go", because three subjects place overlays perfectly well while never escaping anything.
| Escape | Subjects | Count |
|---|---|---|
| Escapes the application window — onto the desktop, past every app-owned edge | Apple, Avalonia (in its PopupRoot configuration), Compose, GTK4 (every backend but Android), posframe + company (GUI child frames), Qt Quick Controls, Qt Widgets, Slint (its TopLevel and native-menu paths), WinUI, WPF, xdg_positioner | 11 |
| Escapes every in-app clip, bounded by the app/terminal surface | Angular CDK, Ariakit, Base UI, Blink, CSS anchor, Dear ImGui, Floating Vue, Flutter, GPUI, Headless UI, Helix, Neovim, Notcurses, nui.nvim, nvim-cmp / blink.cmp, Popover API, Radix, React Aria, Textual, Tippy, tmux, Turbo Vision, Uno, Zag | 24 |
| Clipped by an ancestor, or the subject does not answer | WAI-ARIA APG (position: absolute inside the trigger's containing block — no portal anywhere in the corpus), Floating UI (emits coordinates; escaping is the caller's job), Ratatui (a widget can only write inside its own Rect) | 3 |
NOTE
Textual is the one subject that implements a genuine first-class top layer in integer cells with no OS window and no z coordinate, and it decomposes into exactly three resets: an order reset, a clip reset, and an extent reset that keeps a wide overlay from growing its host's scrollable size. The third appears in no other subject.
Milestones
When the capabilities the field now takes for granted actually landed. The Verified column is strict: ✓ means the fact and its date were read at the pinned revision recorded in the ledger; ⚠ means the fact is grounded but the calendar date comes from ecosystem knowledge outside the reading, and should be treated as approximate.
| When | Milestone | Subject | Verified |
|---|---|---|---|
| ≈1990 | The whole overlay vocabulary — dropdown menus, cascading submenus, context menus, modal dialogs, a combobox surface, drop shadows — already exists inside a character cell grid, with no compositor, no hover, no key releases and no timers | Turbo Vision | ⚠ era attribution; the tree read is magiblot/tvision, a modern port, and carries no such date |
| 2006 (WPF 3.0) | BetweenShowDelay — the cool-down / skip-delay as one duration plus one flag, defaulting to 100 ms | WPF | ⚠ "shipping since WPF 3.0" is read at the pinned revision; the calendar year is not |
| ≈2016 (xdg-shell stable) | set_constraint_adjustment makes flip/slide/resize a wire-level bitfield the client cannot compute, solved out of process | xdg_positioner | ⚠ date not established at the pinned revision; what is pinned is that the last functional change was interface version 3, and the interface stands at version 7 in wayland-protocols 1.49 |
| ≈2020 (GTK 4.0) | GtkPopover becomes a GtkNative with its own GdkSurface, and GdkPopupLayout becomes the ten-scalar value a compositor or GDK's own integer solver executes | GTK4 | ⚠ the 4.0 attribution was not read at the pinned revision (4.23.1) |
| ≈2020 (Popper 2 → Floating UI) | Placement becomes an ordered middleware fold over one mutable (x, y, placement, rects, middlewareData) tuple, with every environment contact behind a three-method Platform | Floating UI | ⚠ dates; the packages read are @floating-ui/dom 1.8.0 / @floating-ui/react 0.27.20 |
| ≈2023–2024 | The HTML Popover API and the top layer specify stacking, cascade dismissal, focus scope and re-entrancy in painstaking detail — and specify placement, timing, hover and semantics not at all | Popover API, Blink | ⚠ shipping dates were not read; the algorithms were |
| ≈2024 | CSS anchor positioning puts placement into the cascade — anchor(), position-try-fallbacks, and an incumbent-skip rule that trades optimality for stability | CSS anchor | ⚠ ship date; at the pinned revision Level 1 is a Working Draft with Work Status "refining" |
| March 2020 → January 2026 | Ratatui's Clear — the one widget whose purpose is popups — panics when its rect crosses the right or bottom edge, for six years | Ratatui | ✓ |
| April 2025 | Ratatui absorbs the centered_rect helper out of an example and into Rect, five years after it appeared | Ratatui | ✓ |
| August 2025 | Ariakit patches tabindex across three separate open paths in one commit (ccfa79e8) — the cost of an overlay family with no single open funnel | Ariakit | ✓ |
| December 2025 | CSS anchor-name lookup is re-specified to match timeline-name lookup (nearest ancestor first, else last match in tree order) | CSS anchor | ✓ |
| 2026-04-28 | The ::tether pseudo-element — the arrow primitive — is reverted back out of the anchor-positioning drafts (31491a8b4), leaving Level 1 with no arrow concept at all | CSS anchor | ✓ |
| GTK 4.22 | GtkPopoverBin lands as the dedicated context-menu container | GTK4 | ✓ version; ⚠ calendar date |
| Angular CDK v22 | The overlay's default layering becomes the browser top layer (popover="manual" + showPopover()), chosen once at creation by a feature test | Angular CDK | ✓ |
| notcurses 3.0.9 | The ncplane_move_family_* reconciliation stops looping forever — evidence that reconciling an ownership tree with a z-order is genuinely hard | Notcurses | ✓ version; ⚠ calendar date |
tmux next-3.8 | Three successive generations of the popup idea sit in the tree at once, with the maintainer's written verdict on the oldest | tmux | ✓ |
| posframe 1.5.2 (2026-05-27), company 1.1.0 (2026-07-21) | The poshandler — placement as a named pure function of a flat measured record — paired with a second, surface-free renderer for the terminal | posframe + company | ✓ |
WARNING
Entries marked ⚠ are not re-verifiable from this tree. They are included because a timeline with only the dates that happened to be committed to source comments would misrepresent the field's shape — but any of them should be re-checked before being quoted as fact.
Quick navigation
Reading paths
"I have twenty minutes." concepts.md for the vocabulary → comparison.md at a glance for the twelve-capability matrix → the ten questions.
"I am designing the Sparkles primitive this informs." The intended path, in order:
sparkles-baseline.md— whatsparkles:uican express today:WidgetKind.popupas a look with no behaviour, the unfinishedPalettepopup metrics,clampOrigin/effectivePopupWidthas the entire current placement engine, the GUI-onlyHoverPopup,DCK5's finished overlay view with nowhere to live, the open defects, and the per-target constraint list.comparison.md§ the ten questions — especially Q1 (the minimal core), Q2 (placement as a pure function), Q3 (what the cell grid costs) and Q9 (what not to foreclose).comparison.md§ the genuine forks — the decisions the field does not agree on, which are therefore decisions rather than defaults.features-people-forget.md— read before freezing any signature; it is the list of behaviours that are cheap to design in and expensive to retrofit.proposal.md— the milestoned plan, then its open questions.- The three examples:
place-reference.d,tooltip-timing.d,dismissal-policy.d— the conclusions as code CI runs.
"I am writing the placement solver." GTK4 (best in class: complete, pure integer, per-axis, value-shaped) → xdg_positioner (the same algebra proven across a process boundary) → Angular CDK (runner-up: a total order over candidates) → comparison.md § 2 and § 3 → place-reference.d.
"I am writing tooltip timing." WPF (BetweenShowDelay, the original formulation) → React Aria (the document-global warm-up machine) → concepts.md § warm-up / cool-down → features-people-forget.md § Timing → tooltip-timing.d.
"I am writing dismissal, focus and modality." Qt Quick Controls (policy as data) → Popover API + Blink (the cause set and the cascade) → Flutter (the hit-group test with no grab) → comparison.md § 8 → dismissal-policy.d.
"I am on a cell grid and want to know what survives." Textual (a real top layer in integer cells) → Turbo Vision (the whole vocabulary, thirty-five years early, and the substitute-don't-omit rule) → Helix and tmux → comparison.md Q3.
"I care about accessibility and the a11y tree." WAI-ARIA APG (roles, focus ownership, dismissal, modality) → Apple → features-people-forget.md § Accessibility → comparison.md § 13.
Neighbouring trees
| Tree | What it carries that this one does not |
|---|---|
| Window system integration | The compositor grab, the X11 override-redirect alternative, and the in-canvas fork as a windowing decision |
| Platform UI guidelines | The platform conventions an overlay's chrome must respect — appearance, contrast, accent, motion |
| UI layout | The box-flow model an overlay's content is laid out by |
| Sean Parent: Better Code | The value-semantics vocabulary the recommendations lean on — Regular types, local reasoning, narrow contracts |
| TUI libraries | The cell-grid rendering substrate the terminal subjects sit on |
Specs this survey is tested against
UI principles (PRN1–PRN12) · state machines (STM1–STM13) · the input model (the tier ladder, the hit-testing model) · container routing precedence (DCK13's empty top-layers rung, and DCK5) · backend degradation (TGT5) · the widget catalog (WGT7's popup, WGT16's toast) · the theme.
Sources
- The thirty-eight per-subject deep-dives listed in the master catalog, each carrying its own
Sourcessection of primary-source file paths and official-doc URLs at the revision recorded in the ledger. concepts.md— the shared vocabulary, with an index of which subject exercises each term.comparison.md— the cross-subject synthesis, including how this survey was verified (the counts and the failure taxonomy of the adversarial pass).features-people-forget.md,sparkles-baseline.mdandproposal.md— the behaviour inventory, the current state ofsparkles:ui, and the plan.- The three CI-compiled examples under
examples/. - Writing Research Docs — the conventions this tree follows.