Popover
<oblyx-popover> is a click-triggered surface anchored to a trigger button, built for content that needs real interaction: a form, a list of controls, anything more than a hint. For advisory text on hover, use tooltip instead; for a list of actions, use menu. All three sit on the same shared anchored-layer positioning mechanism, and differ in dismissal and focus behaviour, covered below.
Basic popover
Signed in as jane@example.com.
<oblyx-popover label="Account menu">
<oblyx-popover-trigger>Account</oblyx-popover-trigger>
<oblyx-popover-content>
<p>Signed in as jane@example.com.</p>
</oblyx-popover-content>
</oblyx-popover>oblyxPopover(label = "Account menu") {
oblyxPopoverTrigger { +"Account" }
oblyxPopoverContent {
p { +"Signed in as jane@example.com." }
}
}Composition
A popover needs to keep two independent pieces of caller content apart (the trigger's own content and the popup's content), so it takes two designated children instead of one:<oblyx-popover-trigger> always renders a real <button> around its content, and <oblyx-popover-content> is itself the anchored popup (it carries popover="auto", role="dialog" and tabindex="-1" directly, set once on connect). Both are independently constructible: an HTMX fragment can swap in a bare <oblyx-popover-content> with no <oblyx-popover> wrapper and it still renders correctly.
Placement and alignment
placement (above/below/start/end, default below) sets which side of the trigger the popup opens on;alignment (start/center/end, default start) sets where it sits along the perpendicular axis.
Popover placement and alignment
Opens above the trigger, aligned to its start edge.
Opens to the end side of the trigger, centered on it.
<oblyx-popover placement="above" alignment="start" label="Above, start-aligned">
<oblyx-popover-trigger>Above / start</oblyx-popover-trigger>
<oblyx-popover-content><p>Opens above the trigger, aligned to its start edge.</p></oblyx-popover-content>
</oblyx-popover>
<oblyx-popover placement="end" alignment="center" label="End, center-aligned">
<oblyx-popover-trigger>End / center</oblyx-popover-trigger>
<oblyx-popover-content><p>Opens to the end side of the trigger, centered on it.</p></oblyx-popover-content>
</oblyx-popover>oblyxPopover(placement = OblyxPopoverPlacement.ABOVE, alignment = OblyxPopoverAlignment.START, label = "Above, start-aligned") {
oblyxPopoverTrigger { +"Above / start" }
oblyxPopoverContent {
p { +"Opens above the trigger, aligned to its start edge." }
}
}
oblyxPopover(placement = OblyxPopoverPlacement.END, alignment = OblyxPopoverAlignment.CENTER, label = "End, center-aligned") {
oblyxPopoverTrigger { +"End / center" }
oblyxPopoverContent {
p { +"Opens to the end side of the trigger, centered on it." }
}
}Opening and dismissing
Clicking the trigger toggles open. Closing is otherwise entirely native: the popup carries popover="auto", which gives light-dismiss (a click anywhere outside it) and Escape for free, with no listener of Oblyx's own. The browser's own toggle event reconciles the open property back to whatever the platform actually did, which matters for one non-obvious reason: toggle is asynchronous. showPopover() and hidePopover() take effect immediately, but the event announcing that arrives on a later task, so anything derived from it (including aria-expanded on the trigger) trails the real popup state by a tick. This never affects what a user sees or does; it only matters if you're writing a test or a script against this component's own reactive properties rather than reading :popover-open or focus directly.
Focus trapping
Popover is the one component in this family that traps focus: tooltip and menu deliberately don't, and that's not an inconsistency. A tooltip is advisory text that must never steal keyboard focus, and a menu needs its own roving-focus keyboard model (Tab closes it and continues to the next page element) rather than a generic trap. A popover's content is arbitrary interactive markup (a form, in the example below), so it gets the real thing: opening moves focus into the content (its first focusable descendant, or the content element itself as a fallback), Tab and Shift+Tab cycle only among that content's own focusable descendants, and closing restores focus to the trigger.
Popover with a form, focus trapped inside it
<oblyx-popover label="Rename board">
<oblyx-popover-trigger>Rename</oblyx-popover-trigger>
<oblyx-popover-content>
<label for="ox-doc-popover-form-input">Board name</label>
<input id="ox-doc-popover-form-input" type="text" value="Q3 roadmap" />
<oblyx-button variant="primary" size="sm">Save</oblyx-button>
</oblyx-popover-content>
</oblyx-popover>oblyxPopover(label = "Rename board") {
oblyxPopoverTrigger { +"Rename" }
oblyxPopoverContent {
label {
htmlFor = "ox-doc-popover-form-input"
+"Board name"
}
input(type = InputType.text) {
id = "ox-doc-popover-form-input"
value = "Q3 roadmap"
}
oblyxButton(variant = OblyxButtonVariant.PRIMARY, size = OblyxButtonSize.SM) { +"Save" }
}
}Positioning
Placement comes from CSS anchor positioning (anchor-name, position-anchor,position-area, position-try-fallbacks) computed per instance and set as inline styles: there is no JavaScript measurement and no positioning library involved.position-try-fallbacks is the platform's own viewport-edge collision handling: if a popover would overflow the viewport at its requested placement, the browser tries a flipped placement on its own, with no script involved in that decision either.
CSS anchor positioning isn't supported in every browser yet. Where it isn't, the popup still opens and closes correctly (dismissal, focus and everything else on this page are unaffected), but it renders at a fixed default position disconnected from its trigger rather than anchored beside it: positioning is the only piece that degrades, not the popover's functionality.
Reference
<oblyx-popover>
| Attribute | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Whether the popup is open. A plain reflected attribute/property. Toggled by clicking the trigger, by the platform's own light-dismiss/Escape, or directly by a caller. |
placement | abovebelowstartend | below | Placement relative to the trigger. |
alignment | startcenterend | start | Alignment along the axis perpendicular to placement. |
label | string | undefined | – | Accessible name for the popup's role="dialog" region, set as aria-label on <oblyx-popover-content>. Required; a dialog with no accessible name is a real defect, not a cosmetic gap. |
<oblyx-popover-trigger>
This element declares no attributes.
<oblyx-popover-content>
This element declares no attributes.