HabPopover
A sheet of arbitrary content anchored to a trigger — filter forms, color pickers, detail peeks, inline confirms.
<HabPopover title="Filter sessions" close-button>
<template #trigger>
<HabButton
variant="outlined"
tone="neutral"
>Filters</HabButton
>
</template>
<HabCheckbox label="Errors" />
<template #footer>
<HabButton size="sm">Apply</HabButton>
</template>
</HabPopover>Which anchored sheet
Four things in this system anchor a sheet to a trigger, and they differ by what the sheet holds:
| Component | Content |
|---|---|
HabTooltip | a label, nothing interactive |
HabMenu | a list of actions |
HabSelect | a list of options |
HabPopover | anything that is none of those |
HabModal is the escalation. A popover does not trap focus, does not scrim, and dismisses on a stray click — so it must never hold the only copy of unsaved work, or a destructive confirmation.
Placement is a preference
The sheet flips to the opposite side when the requested one has less room, and clamps into the viewport. Set :flip="false" to pin it exactly, even off-screen.
The resolved position lands on the sheet as data-side and data-align, so a flip is observable rather than silent — useful for pointing an arrow, or for a test.
The sheet is portalled to <body>, which is not incidental: an absolutely positioned sheet is clipped by any overflow: hidden ancestor, and bordered lists, cards, panels and table cells all clip.
Padding
none | sm | md | lg, on the 4px grid. none is the one that matters — set it when the child should reach the edges, like a list or a picker grid.
The design contract's default is a free-form 14px, which is not on the grid. This rounds down to 12, the way the type ladder rounds fractional sizes down.
Dismissing
Closes on outside click and Esc, both from the primitive.
dismissOnClickInside closes on any click inside, for a sheet that is a one-shot picker. Never set it on a sheet holding a form — a stray click would discard what the user typed.
It listens for a click, anywhere in the content — the padding and the gaps between items included — and not for a key. A picker whose choices are only part of the sheet should close on its own commit instead, holding open itself; HabColorPicker does.
Accessibility
- The sheet is a
role="dialog". Give it atitlewhen its contents need naming; that becomes the accessible name. - Focus moves into the sheet on open and returns to the trigger on close, from the primitive. It does not trap — Tab leaves, which is correct for a popover and wrong for a modal.
- The close affordance carries
aria-label, fromclose-label. There is no default — the string is yours to supply, andclose-buttonwithout it warns. - The trigger slot is rendered as the anchor rather than wrapped, so your own button keeps its accessible name.
- Known limitation: with the sheet open, axe reports
aria-hidden-focusagainst reka's own focus sentinels. Primitive markup, not ours — see design-source.md.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
closeButton | boolean | false | Adds a close affordance to the header. |
closeLabel | string | undefined | Accessible name for that close affordance. Required whenever closeButton is set: Habitus ships no default, so the string is the host application's to translate. |
disabled | boolean | false | Blocks the trigger and refuses to open. It belongs on the popover rather than on the caller's own trigger markup: the trigger is a slot, so a caller who passes their own cannot stop the sheet opening by disabling it. |
dismissOnClickInside | boolean | false | Closes on any click inside, for a sheet that is a one-shot picker. Never set it on a sheet holding a form: a stray click would discard what the user typed. |
flip | boolean | true | Set false to pin placement exactly, even off-screen. |
maxHeight | string | "340px" | The sheet scrolls past this — any CSS length. |
open | boolean | undefined | Controlled open state. Omit for self-managed. |
padding | "md" | "sm" | "lg" | "none" | "md" | — |
placement | "bottom-start" | "bottom" | "bottom-end" | "top" | "top-start" | "top-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | "bottom-start" | — |
title | string | undefined | Optional header row. Add it when the sheet's contents need naming. |
width | string | "280px" | Sheet width — any CSS length. |
Events
| Event | Payload | Description |
|---|---|---|
update:open | open: boolean | — |
Slots
| Slot | Bindings | Description |
|---|---|---|
default | any | The sheet's content. |
footer | any | Sticky action row at the bottom — usually Cancel then Apply. |
trigger | any | The anchor — a button, a chip, anything clickable. |