Skip to content

Liquid tab bar

A liquid glass tab bar with a stretchy selection lens and a detached search button.

Installation

  1. 1

    Install the packages

    pnpm add motion clsx tailwind-merge
  2. 2

    Add the utility file

    lib/utils.ts
    import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";
    export function cn(...inputs: ClassValue[]) {  return twMerge(clsx(inputs));}
  3. 3

    Copy and paste the code into your project

    Loading source…
  4. 4

    Update the import paths to match your project

    Point @/lib/utils to your utility file if your path aliases differ.

Usage

app/page.tsx
"use client"
import { LiquidTabBar } from "@/components/ui/liquid-tab-bar"
export function Demo() {  return (    // hrefs make it track the url on its own, no value needed    <LiquidTabBar      items={[        { label: "Home", href: "/", icon: <HouseIcon /> },        { label: "Changelog", href: "/changelog", icon: <BookIcon /> },        { label: "About", href: "/about", icon: <InfoIcon /> },      ]}      onSearch={(query) => setQuery(query)}    />  )}

Options

Pick a value to see the prop you would pass. Defaults are left out.

size
snippet.tsx
<LiquidTabBar />

Props

PropTypeDefaultDescription
items*(string | { label: string; href?: string; icon?: ReactNode })[]Default: -Tabs. An item with an icon shows only the icon and uses its label as the accessible name; an item with href is a link.
valuenumberDefault: -Index of the selected tab. Pass it to control the bar yourself; leave it out and the bar follows the url when the items have hrefs, and its own state otherwise.
defaultValuenumberDefault: 0Index the bar starts on when it is uncontrolled. Ignored once value is passed, and ignored when an item href matches the current url.
onChange(index: number) => voidDefault: -Fires with the index of the tab that was clicked.
size"xs" | "sm" | "md" | "lg"Default: "md"Scale of the bar. Sets the tab padding and icon size, and the search button follows it.
activeColorstringDefault: "#0A84FF"Color of the selected tab's icon or label.
onSearch(query: string) => voidDefault: -Shows the detached search button. Fires with the query as it is typed, and with an empty string when the search closes. Leave it out to hide search.
searchLabelstringDefault: "Search"Placeholder and accessible name of the search field and its button.
closeLabelstringDefault: "Close search"Accessible name of the button while search is open.
classNamestringDefault: -Extra classes merged onto the root element (data-slot="liquid-tab-bar"). The bar sizes to its content and never wraps.

Click a tab and the glass lens slides to it, stretching on the way like liquid. The search button beside the bar opens a search field and turns into a close button; Escape closes it too.

Credits and license

Most components here are recreations of great work from around the web. I don't claim to be the original creator - this is my attempt to reverse-engineer, replicate, and often add a few extra features. I've tried to credit everyone; if I missed someone, let me know.

  • Free to use and modify in both personal and commercial projects.
  • Attribution to Mox UI is appreciated when using a component.
  • Please don't resell the components as your own kit.
Contact Mox UI