Liqui Design

Button

A refracting glass surface by default, and a solid fill when it carries a brand colour.

Loading…

Installation

npx shadcn@latest add https://liqui.design/r/button.json

This writes components/ui/button.tsx into your project, adds the glass design tokens to your globals.css, and installs @liqui-design/glass — the refraction kernel, which stays a dependency because it is not something you should be editing. Everything you would want to edit is in the file you just received.

Installing more than one? Register the namespace once in components.json and drop the URLs:

components.json
{
  "registries": {
    "@liqui-design": "https://liqui.design/r/{name}.json"
  }
}
npx shadcn@latest add @liqui-design/button

Usage

import { Button } from '@/components/ui/button';
<Button variant="accent">Save</Button>

Examples

Variants

Three variants, two materials. glass is the lens. accent and danger are solid fills with no backdrop filter behind them at all.

Loading…

That split is the library's rule and it is Apple's: glass is for a surface you act through, a solid fill is for something that carries meaning. A tinted lens contradicts itself — the point of the colour is that it says this is the one, and the point of the lens is that it lets the wallpaper through. The accent variant used to be a retint of --lq-tint, which meant it was one blue over a bright photograph and a noticeably different one over a dark backdrop. It is now --lq-accent, and it is that colour everywhere.

Retune the solid fills through --lq-accent and --lq-danger; there is no glass underneath to compose with, so a plain background override works too.

Tuning the glass

The glass prop reaches the surface underneath. frost is the material density dial: 0 is Apple's "clear" tier, 1 is "regular". Refraction and bezel control how wide and how strong the lens at the rim is.

Loading…

A solid variant has no surface to reach, so the optical dials are ignored there. radius is the exception — it is geometry rather than optics, and a round accent button still has to be able to say so:

<Button variant="accent" glass={{ radius: 21 }} aria-label="Play">
  <PlayIcon />
</Button>

Disabled

Loading…

Notes

Only the glass variant opts out of <button>

The glass anatomy is four stacked layers plus a content wrapper. A native <button> only accepts phrasing content, so those wrapper <div>s would be invalid HTML inside one. The glass variant passes nativeButton={false}, which is Base UI's supported escape — useButton then supplies role="button", tabIndex and the Enter/Space handlers itself.

accent and danger have no anatomy to put anywhere, so they are real <button> elements, and the thing nativeButton={false} cannot give back comes back with them: implicit form submission.

// Submits the form. No `nativeButton`, no workaround.
<Button type="submit" variant="accent">Claim handle</Button>

It follows that a composing trigger only needs telling about the glass variant:

<DialogClose nativeButton={false} render={<Button>Cancel</Button>} />
<DialogClose render={<Button variant="accent">Copy link</Button>} />

Passing nativeButton={false} alongside a solid Button is not harmless — Base UI would put role="button" and its own key handling on an element that already has both.

Backdrop

A glass button on a flat fill is just a grey box. Put it over imagery, a gradient, or anything with edges for the bezel to bend. See Glass for the full material guide.

The solid variants have no such requirement, which is a second reason to reach for one: an accent button is the button that has to work on every backdrop your app will ever have.

Browser support

Refraction renders in Chromium only. Safari and Firefox drop backdrop-filter entirely when it references an SVG filter, so liqui detects this and falls back to frosted blur — no configuration needed, but do check both.

The solid variants render identically everywhere: there is no filter to fall back from.

On this page