---
title: "FlatDropdown"
description: "A floating dropdown panel whose trigger and content come from slots."
category: Navigation
status: beta
since: 0.3.9
tags: [dropdown, flat, navigation, floating, menu]
syncStatus: migrated
verified: false
---

## Usage

### Basic
Hovering the trigger opens the panel; `close-on-content-click` dismisses it after any click inside.
:::TuffDemoWrapper{demo="FlatDropdownBasicDemo" code-lang="vue"}
---
code: |
  <template>
    <TxFlatDropdown trigger="hover" close-on-content-click>
      <template #trigger="{ open }">
        <TxButton size="sm" :variant="open ? 'primary' : 'secondary'">Actions</TxButton>
      </template>

      <button @click="lastAction = 'Duplicate'">Duplicate</button>
      <button @click="lastAction = 'Rename'">Rename</button>
    </TxFlatDropdown>
  </template>
---
:::

### Best Practices

- Use `trigger="click"` for destructive or state-changing menus; hover-opened panels are easy to trip on a trackpad.
- The hover bridge and safe triangle handle diagonal travel into the panel, so don't raise `closeDelay` for it.
- Set `teleport="false"` when the panel must inherit its container's stacking or clipping context; inline, it is clipped by ancestor `overflow: hidden`.
- When the panel flips to the other side of the trigger, use the `side` slot prop to flip your own decorations, such as arrows and shadows.
- Placement is written back asynchronously after opening; measure absolute geometry in a `requestAnimationFrame`.

## API Reference

### Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `modelValue` | `boolean` | `undefined` | Open state (`v-model`); omit it to leave the panel uncontrolled. |
| `trigger` | `'hover' \| 'click' \| 'manual'` | `'hover'` | How the panel is summoned. |
| `placement` | `Placement` | `'bottom-start'` | Floating placement relative to the trigger. |
| `offset` | `number` | `10` | Gap between trigger and panel, in px. |
| `openDelay` | `number` | `0` | Delay before opening on hover or focus, in ms. |
| `closeDelay` | `number` | `600` | Delay before closing after the pointer leaves, in ms. |
| `exitDuration` | `number` | `280` | Duration of the scale + blur exit animation, in ms. |
| `disabled` | `boolean` | `false` | Disables every interaction. |
| `teleport` | `boolean \| string` | `'body'` | Teleport target; `false` renders inline. |
| `matchTriggerWidth` | `boolean` | `false` | Matches the panel's min-width to the trigger width. |
| `width` | `number \| string` | `undefined` | Fixed panel width, as px or any CSS length; overrides `matchTriggerWidth`. |
| `closeOnClickOutside` | `boolean` | `true` | Closes on a click outside the trigger, panel, and hover bridge; ignored under `manual`. |
| `closeOnEsc` | `boolean` | `true` | Closes on Escape. |
| `closeOnContentClick` | `boolean` | `false` | Closes after any click inside the panel. |
| `panelClass` | `TxFlatDropdownClass` | `undefined` | Extra classes merged onto the panel element. |

### Events

| Event | Payload | Description |
|---|---|---|
| `update:modelValue` | `boolean` | Fires when the open state changes. |
| `open` | — | Fires when the panel opens. |
| `close` | — | Fires when the panel closes. |

### 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. |

## Overview

- `hover` opens on pointer enter or focus and closes after `closeDelay`. `click` toggles on click without `closeDelay`. `manual` never opens by itself; `v-model` drives it, for panels that follow app state, such as a shortcut.
- A hover bridge covers the gap between trigger and panel. The safe triangle keeps the panel open while the pointer heads for it; `closeDelay` starts only when the pointer strays, stops, or leaves away from the panel.
- Without `v-model`, the component tracks its own open state. With it, the host writes the value back; `open` / `close` still fire.
- `disabled` blocks every opening path, including the trigger slot's `show()`.
- Outside click, Escape, and content click are independent dismissal paths; under `manual`, an outside click does not close.
- The trigger wrapper carries `aria-haspopup`, `aria-expanded`, and an `aria-controls` that points at the panel's id.

## Technologies

- The hover bridge and safe triangle come from `packages/tuffex/packages/utils/hover-intent.ts`, shared with the anchor family. The last Floating UI middleware computes the bridge, which renders as the panel's sibling.
- Source: `packages/tuffex/packages/components/src/flat-dropdown/`.
