# Gooey Nav

> A React navigation bar with a gooey effect: the selected item stretches away from the group.

- Type: React component (Navigation)
- Access: Free (the source is open to everyone)
- Version: 1.0.1
- Install: `npx wingo-ui@latest add gooey-nav`
- Docs: https://wingo-ui.com/components/gooey-nav

## Interaction

Click an item to lift it out of the bar. A gap opens on each side of it, a stretchy neck thins and snaps as the tiles part, and the rest close back into one piece behind it. With hrefs, the nav follows the current URL on its own. On phones the demo docks it under a page as a stacked, full width tab bar.

## Installation

```bash
npx wingo-ui@latest add gooey-nav
```

The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add https://wingo-ui.com/r/gooey-nav.json`

Files:

- `components/ui/gooey-nav.tsx`

npm dependencies: `npm i motion`

Wingo UI dependencies (installed automatically):

- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
"use client"

import { House } from "lucide-react"
import { GooeyNav } from "@/components/ui/gooey-nav"

export function Demo() {
  return (
    // hrefs make it track the url on its own, no value needed
    <GooeyNav
      items={[
        { label: "Home", href: "/", icon: <House /> },
        { label: "Changelog", href: "/changelog" },
        { label: "About", href: "/about" },
      ]}
    />
  )
}

// strings render buttons instead, for a segmented control:
// <GooeyNav items={["Day", "Week", "Month"]} onChange={setRange} />

// size is a single value, so step it down yourself on small screens:
// <GooeyNav items={items} size={useIsMobile() ? "xs" : "md"} />

// or dock it under a phone screen like a tab bar:
// <GooeyNav items={items} size="sm" layout="stacked" fullWidth />
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | `(string \| { label: string; href?: string; icon?: ReactNode })[]` |  | Nav entries. A string is a button, an object with href is a link, and icon renders before the label. |
| `size?` | `"xs" \| "sm" \| "md" \| "lg"` | `"md"` | Scale of the bar. Sets the label padding, text size and icon size, and picks the matching separation and radius so the shape stays in proportion. Use xs on phones, where it fits a 360px screen. |
| `layout?` | `"inline" \| "stacked"` | `"inline"` | Inline puts the icon beside the label. Stacked puts it above, like a phone tab bar, which keeps four items inside a 360px screen at sm. |
| `fullWidth?` | `boolean` | `false` | Stretches the bar across its container and shares the width between the items, for a bar docked to the bottom of a screen. |
| `activeColor?` | `string` | `"#FC4C01"` | Fill of the selected tile. The unselected bar follows barColor. |
| `activeLabelColor?` | `string` | `"#ffffff"` | Label color of the selected item. Set it when a light activeColor leaves white text unreadable. |
| `barColor?` | `string` | `""` | Any CSS color for the unselected tiles and the necks between them. Empty follows the theme: #F4F4F9 light, #262626 dark. |
| `labelColor?` | `string` | `""` | Any CSS color for the unselected labels. Empty uses #6E6D7A light, #9B9AA7 dark. |
| `separation?` | `number` | `20` | Gap in pixels that opens on each side of the selected item, which is what pushes the rest of the bar aside. Leave it out to follow the size: 14 (xs), 16 (sm), 20 (md), 24 (lg). A value you pass overrides the size preset. |
| `radius?` | `number` | `12` | Corner radius of the tiles in pixels. Only the outer corners of the bar and the corners along an open gap use it; a closed seam sits at 0 so the tiles read as one shape. Leave it out to follow the size: 8 (xs), 10 (sm), 12 (md), 14 (lg). A value you pass overrides the size preset. |
| `value?` | `number` |  | Index of the selected item. Pass it to control the nav yourself; leave it out and the nav follows the URL when the items have hrefs, and its own state otherwise. |
| `defaultValue?` | `number` | `0` | Index the nav starts on when it is uncontrolled. Ignored once value is passed, and ignored when an item href matches the current URL. |
| `onChange?` | `(index: number) => void` |  | Fires with the index of the item that was clicked. |
| `className?` | `string` |  | Extra classes merged onto the root element (data-slot="gooey-nav"). The bar sizes to its content and never wraps, so add an overflow container around it if it can outgrow its parent. |

## Parts

### GooeyNavItem (type)

One entry of items: a plain string (a button), or { label, href?, icon? } where href renders a link and icon sits before the label.

## Examples

- **Segmented control**: Plain strings render buttons, so the bar works as a controlled segmented control.
- **Light accent**: Pair a light activeColor with a dark activeLabelColor to keep the label readable.
- **App tab bar**: layout stacked puts icons over labels and fullWidth spreads the tabs across the screen, here in blue.

## See also

- [Nav Tabs](https://wingo-ui.com/components/nav-tabs) ([Markdown](https://wingo-ui.com/components/nav-tabs.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Tabs](https://wingo-ui.com/components/tabs) ([Markdown](https://wingo-ui.com/components/tabs.md))
- [Bottom Tab Bar](https://wingo-ui.com/components/bottom-tab-bar) ([Markdown](https://wingo-ui.com/components/bottom-tab-bar.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))

---

Canonical: https://wingo-ui.com/components/gooey-nav
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
