Components

Button

Glass button that swells under the finger, stretches on drag, and morphs its width and content. Includes a prominent tinted variant.

Press and hold: the glass swells toward you. Tap Edit to morph it into a check.

Installation

pnpm dlx shadcn@latest add https://liquidcn.snmandela.com/r/liquid-button.json

The command copies these files into your project:

  • components/ui/liquid/button.tsx
  • components/ui/liquid/liquid.css
  • lib/liquid/motion.ts
  • lib/liquid/press.ts

It extends the shadcn button component, whichever primitive library yours is built on. If your project doesn't have it yet, the CLI adds it from shadcn's registry; if it does, yours stays as it is, and the CLI asks before replacing it.

Usage

import { Button } from "@/components/ui/liquid/button"

<Button onClick={save}>Save</Button>
<Button variant="prominent" size="icon-lg" aria-label="Done">
  <Check />
</Button>

Examples

Variants

Every shadcn variant, plus prominent: accent-tinted glass for the primary action.

Icons

Icon sizes are 44 px circles, the minimum touch target on iOS.

API reference

Button

Every prop of the shadcn Button is forwarded, including refs and asChild.

PropTypeDefault
variant

prominent is tinted with --liquid-accent, like iOS .glassProminent.

"default" | "prominent" | "secondary" | "outline" | "ghost" | "destructive" | "link""default"
size

Text sizes keep a 44 px minimum height; icon sizes are 44 px circles.

"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"
asChild

Render the child element, such as a link, with the button's glass.

booleanfalse

Motion

ParameterValue
Press swell1 + clamp(8 / √(width × height), 0.04, 0.18)
Press dragRubber band, up to 10 px across and 7 px down
Content popScale impulse of 2.4/s on the press spring
Content morphWidth springs to the new size; new content blurs in over 420 ms

Springs keep their velocity when interrupted, and every value here is removed under reduced motion. How the motion works

Accessibility

  • Renders a native button (or your element with asChild), so focus, Enter, and Space work as usual.
  • Icon-only buttons need an aria-label. When content morphs, update the label with it.
  • Under reduced motion the swell, stretch, and morph are removed; the button still changes instantly.