Skip to content
WANCRAFT

A.02Buttons & Controls

Liquid Button

A button flooded with colour from the exact point the pointer enters, draining out where it leaves.

ReactMotionTailwindintermediate

Overview

Liquid Button measures where the pointer crosses its edge and grows a fill from that point, sized to reach the farthest corner. A pale leading layer and a morphing blob edge read as a liquid meniscus; on leave the fill drains back out through the exit point. Keyboard focus fills from the centre, clicks send a soft ripple, and a loading state swaps the label for a spinner while keeping the button focusable but inert.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/liquid-button.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/liquid-button.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/liquid-button.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
1 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { LiquidButton } from "@/components/ui/liquid-button";

export function LiquidButtonExample() {
  return (
    <LiquidButton onClick={() => console.log("clicked")}>
      Start a project
    </LiquidButton>
  );
}

03Props

PropTypeDefaultDescription
durationnumber600Fill and drain duration in milliseconds.
wobblebooleantrueMorphs the fill's edge like a liquid meniscus while it is visible.
accentstring"#FF7448"Fill colour; also used for the focus ring.
inkColorstring"#0B0D0F"Label and spinner colour while filled. Choose one that contrasts with the accent.
variant"outline" | "ink""outline"Base appearance before the fill arrives.
roundednumber999Corner radius in px.
loadingbooleanfalseShows a spinner, sets aria-busy and aria-disabled, and blocks clicks/form submission while keeping focus.
loadingLabelstring"Loading"Visually hidden text added to the accessible name while loading.
...propsButtonHTMLAttributes—Native button attributes are forwarded (onClick, type, disabled, aria-*). The ref points to the <button>.

04Features

  • 01Fill grows from the exact pointer entry point and drains through the exit point
  • 02Pale leading layer plus a morphing blob edge for a liquid meniscus
  • 03Origin and size are written to CSS variables — no re-render on pointer movement
  • 04Click ripple from the press point
  • 05Loading state with spinner that keeps the button's width stable
motion · tweenmotion · continuousinput · hoverinput · pointerinput · clickinput · focus

05Accessibility

  • Native <button>; Enter and Space work and the ref is forwarded.
  • Keyboard focus (focus-visible) fills from the centre and shows an accent focus ring.
  • While loading: aria-busy and aria-disabled are set, activation is prevented, focus is kept, and a visually hidden loading label is announced with the name.
  • All liquid layers and ripples are aria-hidden.
  • With prefers-reduced-motion the fill cross-fades in place, without scaling, wobble or ripples.

06Limitations

  • The label colour switches to `inkColor` when filled — pair it with an accent that has enough contrast.