---
title: "BaseAnchor"
description: "A primitive that anchors a floating panel to a trigger element."
category: Primitives
status: beta
since: 0.3.4
tags: [popover, overlay, floating, gsap, animation]
syncStatus: reviewed
verified: true
---

## Usage

### Basic
:::TuffDemoWrapper{demo="BaseAnchorBasicDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const open = ref(false)
  </script>

  <template>
    <TxBaseAnchor v-model="open" placement="bottom-start">
      <template #reference>
        <TxButton>Click</TxButton>
      </template>

      Anchored popover content.
    </TxBaseAnchor>
  </template>
---
:::

### Expand Motion
The default `expand` springs open from the corner nearest the reference and folds back on close.
:::TuffDemoWrapper{demo="BaseAnchorSplitLineDemo" code-lang="vue"}
---
code: |
  <template>
    <TxBaseAnchor v-model="open" :animation="{ type: 'expand' }" :show-arrow="true">
      <template #reference>
        <TxButton>Soft Edge</TxButton>
      </template>

      Content emerges from the anchor.
    </TxBaseAnchor>
  </template>
---
:::

### Placement
`placement` is the preferred side; the panel flips when space runs out, and the expand origin follows the final side.
:::TuffDemoWrapper{demo="BaseAnchorPlacementDemo" code-lang="vue"}
---
code: |
  <template>
    <TxBaseAnchor placement="bottom" :show-arrow="true">
      <template #reference>
        <TxButton>Bottom</TxButton>
      </template>
      bottom
    </TxBaseAnchor>
    <TxBaseAnchor placement="top" :show-arrow="true">…</TxBaseAnchor>
    <TxBaseAnchor placement="left" :show-arrow="true">…</TxBaseAnchor>
    <TxBaseAnchor placement="right" :show-arrow="true">…</TxBaseAnchor>
  </template>
---
:::

### Animation Modes
`animation.type` takes `expand` (default), `transfer`, `boom`, `opacity`, or `none`.
:::TuffDemoWrapper{demo="BaseAnchorAnimationDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  const modes = ['expand', 'transfer', 'boom', 'opacity', 'none'] as const
  </script>

  <template>
    <TxBaseAnchor
      v-for="mode in modes"
      :key="mode"
      placement="bottom"
      :animation="{ type: mode }"
      :show-arrow="true"
    >
      <template #reference>
        <TxButton>{{ mode }}</TxButton>
      </template>
      {{ mode }}
    </TxBaseAnchor>
  </template>
---
:::

### Drip
`drip` pours the panel out of its trigger like a drop; items marked `data-liquid-item` appear one by one as the panel grows.
:::TuffDemoWrapper{demo="BaseAnchorDripDemo" code-lang="vue"}
---
code: |
  <template>
    <TxBaseAnchor v-model="open" :width="200" :animation="{ type: 'drip' }">
      <template #reference>
        <button class="workspace-trigger">Select workspace</button>
      </template>

      <button v-for="item in items" :key="item" data-liquid-item>
        {{ item }}
      </button>
    </TxBaseAnchor>
  </template>
---
:::

### Bead
`bead` shares the `drip` engine and pinches its sides by speed; `beadPinch` sets the peak pinch per side in px.
:::TuffDemoWrapper{demo="BaseAnchorBeadDemo" code-lang="vue"}
---
code: |
  <template>
    <TxBaseAnchor v-model="open" :width="200" :animation="{ type: 'bead', beadPinch: 60 }">
      <template #reference>
        <button class="workspace-trigger">Select workspace</button>
      </template>

      <button v-for="item in items" :key="item" data-liquid-item>
        {{ item }}
      </button>
    </TxBaseAnchor>
  </template>
---
:::

### Custom Ease
Duration and easing live in `animation`; the component has no top-level `duration` or `ease` props.
:::TuffDemoWrapper{demo="BaseAnchorCustomEaseDemo" code-lang="vue"}
---
code: |
  <template>
    <TxBaseAnchor :animation="{ type: 'transfer', duration: 600, ease: 'elastic.out(1, 0.4)' }">
      <template #reference>
        <TxButton>Elastic</TxButton>
      </template>
      Elastic easing, 600ms.
    </TxBaseAnchor>
  </template>
---
:::

### Panel Surfaces
`panelBackground` picks the material; `surfaceMotionAdaptation` decides whether it degrades while the panel moves.
:::TuffDemoWrapper{demo="BaseAnchorSurfacePlaygroundDemo" code-lang="vue"}
---
code: |
  <template>
    <TxBaseAnchor
      v-model="open"
      panel-background="glass"
      panel-shadow="medium"
      :panel-card="{ maskOpacity: 0.75 }"
      surface-motion-adaptation="auto"
    >
      <template #reference>
        <TxButton>Open playground</TxButton>
      </template>
      …
    </TxBaseAnchor>
  </template>
---
:::

### Best Practices

- Reach for `TxPopover`, `TxDropdownMenu`, or `TxContextMenu` first; use `TxBaseAnchor` directly only to build a new anchored primitive or to position against a virtual reference.
- When you use it directly, supply the roles, focus handling, and keyboard navigation your content needs.
- Keep panels light; move multi-step forms, destructive confirmations, and full-screen flows to a Drawer or Dialog.
- For coordinate menus, pass `virtualReference` and call `updatePosition()` after the pointer or canvas transform changes.
- `eager` and `keepAliveContent` keep content measurable, not positioned: measure size while closed, and take coordinates from the reference or an open panel.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `modelValue` | `boolean` | `undefined` | Whether the panel is open (`v-model`); omit it for uncontrolled use. |
| `disabled` | `boolean` | `false` | Blocks opening and closes an open panel. |
| `eager` | `boolean` | `false` | Mounts the panel before the first open so its content can be measured. |
| `placement` | `BaseAnchorPlacement` | `'bottom-start'` | Preferred side; flips when space runs out. |
| `offset` | `number` | `8` | Gap to the reference in px. |
| `width` | `number` | `0` | Panel width; `0` sizes to content. |
| `minWidth` | `number` | `0` | Minimum width. |
| `maxWidth` | `number` | `360` | Maximum width. |
| `maxHeight` | `number` | `420` | Maximum height, shrunk to the space left in the viewport. |
| `unlimitedHeight` | `boolean` | `false` | Removes the height limit; `maxHeight <= 0` does the same. |
| `matchReferenceWidth` | `boolean` | `false` | Matches the reference width when `width` is `0`. |
| `referenceClass` | `BaseAnchorClassValue` | `undefined` | Class for the reference wrapper; all other attrs go to the panel. |
| `virtualReference` | `BaseAnchorVirtualReference` | `undefined` | Positions against a virtual reference, such as a cursor point; the `reference` slot still renders. |
| `disableFlip` | `boolean` | `false` | Keeps the requested side but still shifts into view; for a host-measured `virtualReference`. |
| `animation` | `BaseAnchorAnimationOptions` | `{}` | Animation config; omitted fields take the type's defaults. |
| `useCard` | `boolean` | `true` | Wraps content in the built-in `TxCard`. |
| `panelVariant` | `'solid' \| 'dashed' \| 'plain'` | `'plain'` | Border style of the `TxCard`. |
| `panelBackground` | `'pure' \| 'mask' \| 'blur' \| 'glass' \| 'refraction'` | `'refraction'` | Panel background. |
| `panelShadow` | `'none' \| 'soft' \| 'medium'` | `'soft'` | Panel shadow. |
| `panelRadius` | `number` | `18` | Panel corner radius in px. |
| `panelPadding` | `number` | `10` | Panel padding in px. |
| `panelCard` | `BaseAnchorPanelCardProps` | `undefined` | Advanced `TxCard` props, such as `maskOpacity` or `refraction*`. |
| `surfaceMotionAdaptation` | `'auto' \| 'manual' \| 'off'` | `'auto'` | Surface downgrade while moving: `auto` follows the animation, `manual` reads `panelCard.surfaceMoving`, `off` never downgrades. |
| `showArrow` | `boolean` | `false` | Shows an arrow that follows the placement. |
| `arrowSize` | `number` | `10` | Arrow size in px. |
| `keepAliveContent` | `boolean` | `false` | Keeps content mounted after close, with its state. |
| `closeOnClickOutside` | `boolean` | `true` | Closes on an outside click. |
| `closeOnEsc` | `boolean` | `true` | Closes on Escape. |
| `toggleOnReferenceClick` | `boolean` | `true` | Toggles on reference click; turn it off for references that handle their own clicks. |
| `hoverBridge` | `boolean` | `false` | While open, fills the gap to the reference with an invisible hit area; `TxTooltip` enables it when needed. |

### Events

| Event | Params | Description |
|-------|--------|-------------|
| `open` | - | Fires when the panel opens. |
| `close` | - | Fires when the panel closes. |
| `update:modelValue` | `boolean` | Fires when the open state changes. |
| `floating-enter` | `MouseEvent` | The pointer entered the floating layer: the panel or the hover bridge. |
| `floating-leave` | `MouseEvent` | The pointer left the floating layer. |

### Slots

| Slot | Description |
|------|-------------|
| `reference` | The trigger element. |
| `default` | Panel content; receives the final side as `{ side }`. |

### Exposed Methods

| Method | Params | Description |
|--------|--------|-------------|
| `close` | - | Closes the panel. |
| `toggle` | - | Toggles the panel. |
| `updatePosition` | - | Recomputes the position. |
| `getPanelRect` | - | The panel's rect as drawn (`DOMRect`), or `null` before it mounts. |
| `containsFloating` | `(target: Node)` | Whether `target` is inside the floating layer, hover bridge included. |
| `getSide` | - | The final side: `top`, `right`, `bottom`, or `left`. |

### Types

#### BaseAnchorAnimationOptions

| Field | Type | Default | Description |
|------|------|---------|-------------|
| `type` | `'expand' \| 'transfer' \| 'boom' \| 'opacity' \| 'none' \| 'drip' \| 'bead'` | `'expand'` | `expand` springs open, `transfer` slides along the side, `boom` scales out of a blur, `opacity` fades, `none` is instant; `drip` / `bead` are liquid. |
| `closeType` | Same values as `type` | Same as `type` | Type used while closing. Liquid types must match at both ends; a mixed pair falls back to symmetric and warns in dev. |
| `duration` | `number` | per type (expand 400 / classic 432 / liquid 260) | Open duration in ms. |
| `closeDuration` | `number` | per close type (expand 240 / classic: open duration × 0.45 / liquid 150) | Close duration in ms. |
| `ease` | `string` | per type (expand: a spring solved for the panel's height, `spring(10, 0.6)` up to about 63px / classic `back.out(2)` / liquid `linear`) | Open ease: a GSAP ease, `cubic-bezier(...)`, or `spring(omega, zeta)`, run as written; liquid types take only `linear` or `cubic-bezier(...)`. |
| `closeEase` | `string` | per close type (expand `power2.in` / classic `power3.in` / liquid `cubic-bezier(0.25, 0.46, 0.45, 0.94)`) | Close ease; same forms as `ease`. |
| `distance` | `number` | per type (expand 12 / transfer 30) | `expand` drift and `transfer` travel in px. |
| `scale` | `number` | per type (expand 0.88 / boom 0.94 / transfer 0.92) | Starting scale of the open. |
| `blur` | `number` | `12` | Starting blur radius of `boom` in px. |
| `opacity` | `number` | `0` | Starting opacity of `expand`, `boom`, and `opacity`. |
| `exit` | `{ scale?, distance?, blur?, opacity? }` | See description | Close-only geometry; each field falls back to the shared field of the same name, then to `closeType`'s default. |
| `gooBlur` | `number` | `4.5` | `drip` / `bead` only. Goo blur radius; decides how wide a gap the neck survives. |
| `gooThreshold` | `number` | `20` | `drip` / `bead` only. Slope of the alpha threshold. |
| `gooThresholdOffset` | `number` | `-9` | `drip` / `bead` only. Offset of the alpha threshold. |
| `outlineColor` | `string` | `--tx-border-color` | `drip` / `bead` only. Outline ring color; follows the theme by default. |
| `triggerRadius` | `number` | measured | `drip` / `bead` only. Trigger corner radius; measured from the reference when omitted. |
| `seedHeight` | `number` | `12` | `drip` / `bead` only. Panel height at the start, in px. |
| `itemSelector` | `string` | `'[data-liquid-item]'` | `drip` / `bead` only. Items revealed one by one; with no match, the content reveals as a whole. |
| `beadPinch` | `number` | `60` | `bead` only. Peak pinch per side in px; decays to 0 as the motion settles. |
| `beadVelocityRef` | `number` | `4` | `bead` only. Speed at which the pinch peaks. |

## Overview

- With `modelValue` it is controlled; otherwise it keeps its own open state. Each change emits `open` or `close`.
- `maxHeight` shrinks to the space left in the viewport; overflow scrolls inside the card body, so read the scroll position from the body.
- The anchor family draws no arrow by default: BaseAnchor, Tooltip, Popover, and the DropdownMenu, ContextMenu, and Select built on them. A `showArrow` arrow moves with the panel's content layer.
- `drip` / `bead` work on vertical placements only; side placements fall back to `opacity`. They need a measurable height, so `unlimitedHeight` shows and hides instantly.
- `drip` / `bead` paint their own surface: no `TxCard` and no arrow, so panel background, shadow, and variant do not apply. The trigger needs an opaque background.
- Under reduced motion, every animation jumps to its end state.

## Technologies

- The panel is teleported to `<body>` and positioned by Floating UI in document coordinates; GSAP drives the classic types and a rAF loop drives `drip` / `bead`.
- Source: `packages/tuffex/packages/components/src/base-anchor/`.

<TuffDocSourceLink />
