# Gooey Nav

> A navigation bar whose active pill breaks into goo that flows over to the item you pick.

Click an item and the active pill melts into droplets that arc across the bar and pool into a new pill under that item. A WebGL2 metaball shader draws the goo, and the active text is clipped to the same shape. Hover an item to see a soft highlight behind it.

<demo-gooey-nav>



</demo-gooey-nav>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/gooey-nav.json"
```

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import GooeyNav from '~/components/ui/gooey-nav/GooeyNav.vue';

  const items = [
    { label: 'Home', href: '#', icon: 'lucide:house' },
    { label: 'Work', href: '#', icon: 'lucide:briefcase' },
    { label: 'About', href: '#', icon: 'lucide:user' },
    { label: 'Contact', href: '#', icon: 'lucide:mail' },
  ];
  const active = ref(0);
</script>

<template>
  <GooeyNav
    v-model:active-index="active"
    :items="items"
    theme="dark"
    color="#ff5a1f"
    :colors="['#ff5a1f', '#ff8a3a', '#ffb020']"
    :particle-count="15"
    :spread="56"
  />
</template>
```

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        items
      </code>
    </td>
    
    <td>
      <code>
        GooeyNavItem[]
      </code>
    </td>
    
    <td>
      4 samples
    </td>
    
    <td>
      The navigation items. Items without an <code>
        href
      </code>
      
       render as buttons.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        initial-active-index
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Which item starts selected.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        active-index
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Controls the selected item. Use with <code>
        v-model:active-index
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        theme
      </code>
    </td>
    
    <td>
      <code>
        'dark' | 'light'
      </code>
    </td>
    
    <td>
      <code>
        'dark'
      </code>
    </td>
    
    <td>
      Color theme of the frame and text.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Color of the active pill. Uses a high-contrast theme color when unset.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        active-text-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Text color inside the pill. Picked from the pill color when unset.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        colors
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      <code>
        []
      </code>
    </td>
    
    <td>
      Droplet colors. They blend as they merge. Uses the pill color when empty.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        size
      </code>
    </td>
    
    <td>
      <code>
        'sm' | 'md' | 'lg'
      </code>
    </td>
    
    <td>
      <code>
        'md'
      </code>
    </td>
    
    <td>
      Size of the items.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        frame
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Shows the frosted panel around the items.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        animation-time
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        600
      </code>
    </td>
    
    <td>
      Roughly how long the droplets take to reach the new item, in ms.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        particle-count
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        15
      </code>
    </td>
    
    <td>
      How many droplets the pill breaks into.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        spread
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        56
      </code>
    </td>
    
    <td>
      How far the droplets scatter on the way, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        time-variance
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        300
      </code>
    </td>
    
    <td>
      Random variation in each droplet's timing, in ms.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gooeyness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      How readily droplets and pills melt together, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        wobble
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      How much the new pill wobbles as it forms, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hover-effect
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Shows a soft highlight behind the hovered item.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the <code>
        <nav>
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

### GooeyNavItem

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Display text
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        href
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Link URL. Leave it out to render a button.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        icon
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Iconify name, e.g. <code>
        lucide:house
      </code>
    </td>
  </tr>
</tbody>
</table>

## Events

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        change
      </code>
    </td>
    
    <td>
      <code>
        (index: number, item: GooeyNavItem)
      </code>
    </td>
    
    <td>
      Fired when an item is picked.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        update:activeIndex
      </code>
    </td>
    
    <td>
      <code>
        (index: number)
      </code>
    </td>
    
    <td>
      Drives <code>
        v-model:active-index
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- Renders a `<nav>` with real `<a>` links (or `<button>` without `href`). The current item gets `aria-current="page"`.
- Every link has a visible focus ring. The canvas and the clipped text copy are `aria-hidden`.
- With `prefers-reduced-motion: reduce`, the pill jumps to the new item with no droplets.
- Without WebGL2, a plain pill moves to the active item instead.
- Ported from [React Bits](https://reactbits.dev/components/gooey-nav) by David Haz.
