---
title: "Liquid"
description: "A two-layer group whose touching pieces merge like liquid."
category: Effects
status: beta
since: 0.3.9
tags: [liquid, gooey, morph, physics]
syncStatus: reviewed
verified: true
---

## Usage

### Morph
With the default `effect="morph"`, give items `x` / `y`: element and liquid animate together, and touching pieces merge like droplets.
:::TuffDemoWrapper{demo="LiquidMorphMenuDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const open = ref(false)
  </script>

  <template>
    <TxLiquid :blur="8" fill="var(--tx-bg-color)" shadow="0 2px 8px rgba(0,0,0,.14)">
      <TxLiquidItem :x="open ? -64 : 0" :y="open ? -44 : 0" transition="bouncy">
        <TxButton circle variant="ghost" :border="false">✦</TxButton>
      </TxLiquidItem>
      <TxLiquidItem :y="open ? -76 : 0" transition="bouncy" :delay="40">
        <TxButton circle variant="ghost" :border="false">☾</TxButton>
      </TxLiquidItem>
      <TxLiquidItem :x="open ? 64 : 0" :y="open ? -44 : 0" transition="bouncy" :delay="80">
        <TxButton circle variant="ghost" :border="false">♪</TxButton>
      </TxLiquidItem>
      <TxLiquidItem>
        <TxButton circle variant="ghost" :border="false" @click="open = !open">＋</TxButton>
      </TxLiquidItem>
    </TxLiquid>
  </template>
---
:::

### Shape Physics
`morph.shape` turns on shape change: the mass flows to the new centre first, size and radius follow, and content blurs in motion and sharpens as it settles.

```vue
<template>
  <TxLiquid :blur="8" fill="var(--tx-bg-color)">
    <TxLiquidItem :morph="{ shape: true, speed: 1, bounce: 0.5 }">
      <div :class="open ? 'panel-open' : 'panel-closed'">…</div>
    </TxLiquidItem>
  </TxLiquid>
</template>
```

### Dissolve
`dissolve` is orthogonal to `effect`: at the contact point, the item's imagery melts into its neighbour through a turbulence warp, not a blur. Text never melts.

```vue
<template>
  <TxLiquid :blur="10" fill="var(--tx-bg-color)">
    <TxLiquidItem dissolve>
      <img class="avatar" src="/a.png" alt="">
    </TxLiquidItem>
    <TxLiquidItem :dissolve="{ mix: 0.7, active: dragging }">
      <img class="avatar" src="/b.png" alt="">
    </TxLiquidItem>
  </TxLiquid>
</template>
```

### Move Trail
With `effect="move"`, you move the element yourself (CSS or pointer events); the liquid chases it on a spring and drags a droplet tail.
:::TuffDemoWrapper{demo="LiquidMoveTrailDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const stops = [8, 96, 184]
  const pos = ref(0)
  </script>

  <template>
    <TxLiquid :blur="7" fill="var(--tx-bg-color)">
      <TxLiquidItem effect="move" :move="{ trail: 0.6 }">
        <div class="thumb" :style="{ transform: `translateX(${stops[pos]}px)` }" />
      </TxLiquidItem>
    </TxLiquid>
  </template>
---
:::

### Best Practices

- Keep item backgrounds transparent: the liquid is the surface. Opaque content, such as a round photo, covers its own blob.
- Pass a CSS variable as `fill` (e.g. `var(--tx-bg-color)`) so the liquid follows the theme.
- Put shadows on `shadow`, not on children, so one shadow hugs the merged liquid.
- When pieces that should merge look separate, raise `blur` before tuning anything else.
- For high-frequency dragging, start from the `effect="move"` defaults; reach for `advanced` only if they fall short.

## API Reference

### TxLiquid

#### Props

| Prop | Type | Default | Description |
|------|------|---------|------|
| `blur` | `number` | `6` | Goo blur sigma (px); sets how far apart pieces start bridging. |
| `contrast` | `number` | `18` | Alpha-contrast slope; larger values give a sharper liquid edge. |
| `fill` | `string` | `'#fff'` | Liquid surface color; accepts `var()`. |
| `shadow` | `string` | - | `box-shadow` syntax drawn on the merged silhouette; `inset` layers paint inside. |
| `filterPadding` | `number` | `24` | Extra filter-region slack (px) for blobs that leave the group box. |

#### Slots

| Slot | Props | Description |
|------|------|------|
| `default` | - | Several `TxLiquidItem` nodes. |

### TxLiquidItem

#### Props

| Prop | Type | Default | Description |
|------|------|---------|------|
| `effect` | `'morph' \| 'move'` | `'morph'` | Liquid behavior: `morph` merges, `move` chases with a tail. |
| `morph` | `MorphTuning` | - | `shape` / `speed` / `bounce` / `contentBlur`, plus the `advanced` escape hatch. |
| `move` | `MoveTuning` | - | `springiness` / `wobble` / `stretch` / `trail`, plus `advanced`. |
| `dissolve` | `boolean \| number \| DissolveOptions` | - | Contact-melt modifier; `0..1` scales its intensity. |
| `x` / `y` / `scale` | `number` | `0` / `0` / `1` | Component-driven position and scale, pixel-synced with the liquid. |
| `transition` | `'snappy' \| 'smooth' \| 'bouncy' \| SpringConfig \| { duration, ease }` | `'smooth'` | Spring or duration transition for `x` / `y`. |
| `delay` | `number` | `0` | Transition delay in ms, for staggering. |
| `observe` | `boolean` | `false` | Makes the liquid follow content you animate yourself; implied by `morph.shape`, `dissolve`, and `move`. |
| `radius` | `number \| [tl, tr, br, bl]` | measured | Overrides the radius measured from computed style. |

#### Slots

| Slot | Props | Description |
|------|------|------|
| `default` | - | The real interactive content; keep its background transparent. |

## Overview

- `TxLiquid` is a relatively positioned `isolation: isolate` container: the silhouette SVG sits below every child, the melt overlay above the content, and neither takes pointer events.
- `TxLiquidItem` must render inside a `TxLiquid`, or it throws.
- Size the group to contain the items' full travel; bridging starts roughly once `blur ≳ gap`.
- The component owns the wrapper's `transform` (`x/y/scale`), the content `filter` during `morph.shape`, and `mask-image` on `<img>` (`dissolve`); don't set your own.
- `dissolve` with `effect="move"` is ignored with a warning; under reduced motion, component-driven transitions become instant.
- The entry also exports `resolveTransition(transition, reducedMotion?)` and `liquidTransitionPresets`, which compile a preset or `SpringConfig` into `{ duration, easing }` (CSS `linear()`, or cubic-bezier where unsupported) for WAAPI or CSS motion outside the component.

## Technologies

- The silhouette layer merges each item's blob through an SVG goo filter (Gaussian blur plus alpha contrast); the content layer skips the filter and stays crisp and interactive.
- Ported from [Jakubantalik/Libraries · liquid-gooey](https://github.com/Jakubantalik/Libraries/tree/main/packages/liquid-gooey) (MIT © Jakub Antalik); React's `Liquid` / `Liquid.Item` map to `TxLiquid` / `TxLiquidItem`.
- Source: `packages/tuffex/packages/components/src/liquid/`.

<TuffDocSourceLink />
