Non-modal anchored overlay with a trigger slot, open state, dismissal, and collision-aware positioning.
Install @wyrd-company/react or the Flutter package and use it directly —
your DESIGN.md does the styling.
catalog/contracts/popover.ymlsource of truth · verbatim
$schema: https://thewyrdingway.dev/schemas/component-contract.schema.ymlcontractVersion: 0.1.0identity:name: Popovertag: wyrd-popoverpackage: wyrd.component.popovertitle: Popoversummary: Non-modal anchored overlay with a trigger slot, open state, dismissal, and collision-aware positioning.relationships:related-to: - quality-parity-and-gatesclassification:category: overlaycomposition: multi-slotinteractive: truetarget-strategy:react: requiredflutter: requiredsurface:properties: - name: is-opentype:kind: booleanrequired: falsedefault: falsecontrolled: truereflects: trueserializable: truesummary: Controlled popover visibility. - name: default-opentype:kind: booleanrequired: falsedefault: falseserializable: truesummary: Initial uncontrolled popover visibility. - name: placementtype:kind: enumvalues: - top - bottom - start - endrequired: falsedefault: bottomreflects: trueserializable: truesummary: Preferred surface placement relative to the trigger. - name: trigger-modetype:kind: enumvalues: - click - hoverrequired: falsedefault: clickreflects: trueserializable: truesummary: Interaction that opens the popover; hover also supports keyboard focus. - name: dismissibletype:kind: booleanrequired: falsedefault: truereflects: trueserializable: truesummary: Allows Escape and outside pointer dismissal.slots: - name: triggerrequired: truecardinality: exactly-oneaccepts: - componentsummary: Anchor component; clicking it toggles the popover. - name: contentcontent: truerequired: truecardinality: one-or-manyaccepts: - component - content - nodesummary: Popover surface content.events: - name: open-changepayload:is-open:kind: booleanreason:kind: enumvalues: - trigger - hover - focus - escape - outside-pointer - programmaticbubbles: truecomposed: truecancelable: falsesummary: Canonical open-state change event.states: - name: closedsource: behavior-runtimesummary: Popover content is hidden. - name: opensource: behavior-runtimesummary: Popover content is visible and anchored to the trigger.parts: - name: rootsummary: Popover root and event owner.slots: - trigger - content - name: triggersummary: Trigger wrapper.slots: - trigger - name: surfacesummary: Anchored popover surface.slots: - contenttokens:surface:background: color.surface-containerforeground: color.on-surfaceborder-color: color.outlineborder-width: 1pxradius: rounded.mdshadow: shadow.mdpadding-x: spacing.mdpadding-y: spacing.mdoffset: spacing.xsmessages: []obligations:accessibility:role: nonename: Popover surface has no required accessible name and therefore does not force role=dialog in v1.keyboard: - Escape closes the popover and restores focus to the trigger when dismissible is true. - Hover trigger mode opens when keyboard focus enters the trigger and remains open while focus is within the trigger or popover content.semantics: - Popover is non-modal; it does not trap focus and does not make background content inert. - Trigger exposes aria-expanded. - Trigger exposes aria-controls. - Focus moves into the popover only when it contains a focusable descendant.behavior:model: overlayinteractions: - Trigger click toggles the popover. - Hover trigger mode opens after a 200 ms pointer-hover delay and closes after a 100 ms pointer-leave delay. - Hover trigger mode opens immediately when keyboard focus enters the trigger and closes when focus leaves both trigger and content. - Moving the pointer from the trigger into the popover content cancels a pending close so interactive content remains usable. - Escape closes the popover and restores focus to the trigger when dismissible is true. - Outside pointer down closes the popover when dismissible is true. - Positioning uses the package anchoring utility with fixed coordinates, flip, shift, and RTL-aware start/end placement.hostEffects: - Recompute fixed coordinates when the popover opens and while visible on capture-phase scroll and viewport resize. - Anchor tracking during layout animations is not guaranteed in v1.conditions: - rtl - reduced-motion - cvd-deuteranopia - density-compact - text-expansionsdui:status: serializableserializableProperties: - is-open - default-open - placement - trigger-mode - dismissibleserializableSlots: - trigger - contenteventBindings: - open-changenotes: Popover serializes trigger/content slots and open-state data. Web projections use collision-aware fixed positioning through the hand-written package positioning utility. The popover is non-modal; hosts must not infer focus trap or inert background behavior.examples: - name: basicstate: closedproperties:default-open: falseplacement: bottomtrigger-mode: clickslots:trigger: Detailscontent: Additional details for the selected item. - name: openstate: openproperties:default-open: trueplacement: bottomslots:trigger: Detailscontent: Additional details for the selected item. - name: hover-cardstate: closedproperties:trigger-mode: hoverplacement: bottomslots:trigger: Profilecontent: Profile details and actions. - name: nondismissiblestate: openproperties:default-open: truedismissible: falseslots:trigger: Inspectorcontent: Persistent inspector controls.overrides:react:slots:trigger: triggercontent: childrenflutter:slots:content: child
01
Examples
Generated from the contract's examples block.
Previews are the published @wyrd-company/react component, themed by the
brand's role tokens — flip the site's light/dark toggle and they follow.
basicstate: closed
Additional details for the selected item.
Usage
import { WyrdPopover } from"@wyrd-company/react";
export function PopoverInteractionExample() {
return <WyrdPopover placement="bottom" trigger={<button type="button">Details</button>}>
Additional details for the selected item.
</WyrdPopover>;
}
Preferred surface placement relative to the trigger.
trigger-mode
click | hover
click
Interaction that opens the popover; hover also supports keyboard focus.
dismissible
boolean
true
Allows Escape and outside pointer dismissal.
03
Slots & events
Slot
Cardinality
Accepts
Summary
trigger
exactly-one
component
Anchor component; clicking it toggles the popover.
content
one-or-many
component, content, node
Popover surface content.
Event
Payload
Summary
open-change
is-open, reason
Canonical open-state change event.
04
Tokens
The contract's token bindings. Open the shared contextual sidebar from any example;
primary swatches reflect the color you pick there. Error swatches show the active
preset's values; editing any knob switches to custom and resets error to TWW · Ink.
Binding
Token
surface.background
color.surface-container
surface.foreground
color.on-surface
surface.border-color
color.outline
border-width
1px
radius
rounded.md
shadow
shadow.md
padding-x
spacing.md
padding-y
spacing.md
offset
spacing.xs
05
States
State
Source
Summary
closed
behavior-runtime
Popover content is hidden.
open
behavior-runtime
Popover content is visible and anchored to the trigger.
06
Accessibility
From the contract's accessibility obligations. Role none; Popover surface has no required accessible name and therefore does not force role=dialog in v1.
Escape closes the popover and restores focus to the trigger when dismissible is true.
Hover trigger mode opens when keyboard focus enters the trigger and remains open while focus is within the trigger or popover content.
Popover is non-modal; it does not trap focus and does not make background content inert.
Trigger exposes aria-expanded.
Trigger exposes aria-controls.
Focus moves into the popover only when it contains a focusable descendant.
Status: serializable. Popover serializes trigger/content slots and open-state data. Web projections use collision-aware fixed positioning through the hand-written package positioning utility. The popover is non-modal; hosts must not infer focus trap or inert background behavior.