FlatDropdown
Slot-driven floating dropdown panel (`TxFlatDropdown`). Hover / click / manual triggers, flip-aware placement, and a scale + blur exit animation.
Usage
Hover the trigger to open. The gap between trigger and panel is covered by a hover bridge, and a pointer heading for the panel keeps it open through the safe triangle; once the pointer really leaves, the panel stays for closeDelay ms. close-on-content-click dismisses the panel after any click inside.
FlatDropdown (basic)
Best Practices
- Prefer
trigger="click"for destructive or state-changing menus — hover-opened panels are easy to trip over on trackpads. - Diagonal travel into the panel is handled by the hover bridge and the safe triangle, not by
closeDelay;closeDelayonly decides how long the panel stays once the pointer really leaves (off the triangle, stopped, or out through the side facing away from the panel). - Set
teleport="false"when the dropdown lives inside a container with its own stacking or clipping context and you need it to inherit that context. Note that inline rendering re-exposes the panel to ancestoroverflow: hidden. - Use the
sideslot prop to flip your own decorations (arrows, shadows) when the panel flips above the trigger. - Do not rely on the panel being positioned synchronously after opening — placement is written back reactively, so measure in a
requestAnimationFrameif you need absolute geometry. - The reference wrapper advertises the panel with
aria-haspopup,aria-expanded, andaria-controls(pointing at the panel's generated id), so a slotted<button>trigger inherits disclosure semantics automatically.
API Reference
TxFlatDropdown
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | boolean | undefined | Controlled open state. Omit for uncontrolled behaviour. |
trigger | 'hover' | 'click' | 'manual' | 'hover' | How the panel is summoned. |
placement | Placement | 'bottom-start' | Floating placement relative to the trigger. |
offset | number | 10 | Gap in px between trigger and panel. |
openDelay | number | 0 | Delay before opening on hover/focus (ms). |
closeDelay | number | 600 | Delay before closing after pointer leave (ms). |
exitDuration | number | 280 | Duration of the scale + blur exit animation (ms). |
disabled | boolean | false | Disable every interaction. |
teleport | boolean | string | 'body' | Teleport target; pass false to render inline. |
matchTriggerWidth | boolean | false | Match the panel's min-width to the trigger width. |
width | number | string | undefined | Fixed panel width. Overrides matchTriggerWidth. |
closeOnClickOutside | boolean | true | Close when clicking outside. |
closeOnEsc | boolean | true | Close when pressing Escape. |
closeOnContentClick | boolean | false | Close after any click inside the panel. |
panelClass | TxFlatDropdownClass | undefined | Extra class(es) merged onto the panel element. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | boolean | Open state changed. |
open | — | The panel opened. |
close | — | The panel closed. |
Slots
| Slot | Props | Description |
|---|---|---|
trigger | { open, toggle, show, hide } | The anchor element. |
default | { open, close, side } | Panel body. side is the resolved side after flip. |
Trigger Modes
trigger decides how the panel is summoned:
hover(default) — opens on pointer enter / focus, closes aftercloseDelay; a trip towards the panel does not count as leaving.click— toggles on click;closeDelayis not applied.manual— the component never opens itself. Drive it withv-model.
Use manual when the panel must follow application state rather than pointer intent — for example a dropdown that opens as the result of a keyboard shortcut.
Controlled vs Uncontrolled
Omit v-model and the component tracks its own open state. Bind v-model and you own it — the component still emits open / close, but will not change the value itself unless the interaction is allowed.
disabled blocks every opening path, including programmatic ones through the trigger slot's show().
Sizing
By default the panel sizes to its content. Two escape hatches:
match-trigger-width— sets the panel'smin-widthto the measured trigger width. Good for select-like menus.width— a fixed px number or any CSS length. Overridesmatch-trigger-width.
Dismissal Contract
Three independent dismissal paths, each separately switchable:
| Prop | Default | Dismisses when |
|---|---|---|
closeOnClickOutside | true | A click lands outside the trigger, the panel and the hover bridge |
closeOnEsc | true | Escape is pressed |
closeOnContentClick | false | Any click inside the panel |
closeOnClickOutside only applies to the click and hover triggers — under manual the host owns dismissal entirely.
Technologies
- Component:
packages/tuffex/packages/components/src/flat-dropdown/src/TxFlatDropdown.vue - Types:
packages/tuffex/packages/components/src/flat-dropdown/src/types.ts - The hover bridge and the safe triangle come from
packages/tuffex/packages/utils/hover-intent.ts, shared with the anchor family. The bridge's geometry comes from the last middleware in the Floating UI chain; it renders as the panel's sibling with the sameposition: fixedandz-index, and enters and leaves like the panel. - Rejected design: the bridge as a child or pseudo-element of the panel. The panel's
transformbelongs to its scale-in and blur-out motion, and a host'spanelClassmay give the paneloverflow: hidden, which would clip a hit area reaching outside the box. - Verified coverage:
packages/tuffex/packages/components/src/flat-dropdown/__tests__/flat-dropdown.test.tscovers the hover bridge: rendered as the panel's sibling, a pointer resting on it keeps the panel open, a press on it is not an outside click, andclickmode renders none.