Components

Tab Bar

Floating glass tab bar with a search button: search collapses the tabs into a circle and stretches the button into a field, and the two glass surfaces fuse when pressed.

Tap search: the tabs fold into a circle as the button stretches into a field. Hold the button to see the two surfaces fuse.

Contacts

Installation

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

The command copies these files into your project:

  • components/ui/liquid/tab-bar.tsx
  • components/ui/liquid/tabs.tsx
  • components/ui/liquid/liquid.css
  • lib/liquid/motion.ts
  • lib/liquid/press.ts
  • lib/liquid/lens.ts
  • lib/liquid/lens-parts.ts
  • lib/liquid/fusion.ts
  • lib/liquid/bar.ts
  • lib/liquid/refraction.ts
  • lib/liquid/tab-bar.ts

It also installs lucide-react.

It extends the shadcn tabs 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 { TabBar, TabBarItems, TabBarSearch } from "@/components/ui/liquid/tab-bar"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/liquid/tabs"

<Tabs defaultValue="calls">
  <TabBar>
    <TabBarItems icon={<Clock />} label="Back to Calls">
      <TabsList aria-label="Phone" className="liquid-tabbar">
        <TabsTrigger value="calls"><Clock />Calls</TabsTrigger>
        <TabsTrigger value="contacts"><CircleUserRound />Contacts</TabsTrigger>
      </TabsList>
    </TabBarItems>
    <TabBarSearch placeholder="Search" value={query} onChange={search} />
  </TabBar>
</Tabs>

Examples

Text tabs

Any liquid TabsList works inside TabBarItems, like the iPad tab bar. Uncontrolled, the tab bar keeps its own search state.

Made for you

API reference

TabBar

Lays out the tabs and the search button, and owns the search state.

PropTypeDefault
searching

Whether search is open. Leave it undefined to let the tab bar manage it.

booleanNone
defaultSearching

Initial state when uncontrolled.

booleanfalse
onSearchingChange

Called when search opens or closes.

(searching: boolean) => voidNone

TabBarItems

The glass around a liquid TabsList (or a list of links with a lens). While searching, the tabs are inert and a circle stands in for them.

PropTypeDefault
icon

Shown in the circle, usually the selected tab's icon.

ReactNodeNone
label

Accessible name of the circle, which closes search.

string"Show tabs"

TabBarSearch

The search button that becomes the field. Props, ref, and events go to the <input>; className styles the glass.

PropTypeDefault
closeLabel

Accessible name of the close button in the field.

string"Close search"
--liquid-tab-bar-width

Width of the whole bar while searching, set on TabBar.

CSS lengththe bar's resting width

Motion

ParameterValue
Search morphEach part's width springs from the old layout to the new one
Tabs outFade, 6 px blur, and 0.86 scale over 220–360 ms
Field inSearch glass pops with a 2.4/s scale impulse; content condenses in 380 ms
FusionMetaball neck while pressed, spread up to 0.5 of the gap
RefractionRim bends the backdrop up to 60% of an 18 px bezel (Chromium)

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

Accessibility

  • The search button has an accessible name, and opening search moves focus to the field.
  • Escape, the close button, or the circle closes search and returns focus to the search button.
  • While searching, the tabs are inert and hidden from assistive technology, so focus cannot reach what is folded away.
  • The neck and the refraction are decorative: aria-hidden, off under reduced motion, reduced transparency, increased contrast, and forced colors.