# Bubble Menu

> Floating circular expanding menu with staggered item reveal.

A logo bubble and a round menu button float at the top of their container. Click the button and the links pop in as large tilted pills, one after another, each label sliding up as its bubble grows. Hover a pill to tint it. Click the button again and everything shrinks away.

<demo-bubble-menu>



</demo-bubble-menu>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import { BubbleMenu } from '~/components/ui/bubble-menu';
  import type { BubbleMenuItem } from '~/components/ui/bubble-menu';

  const items: BubbleMenuItem[] = [
    {
      label: 'home',
      href: '/',
      rotation: -8,
      hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' },
    },
    {
      label: 'about',
      href: '/about',
      rotation: 8,
      hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' },
    },
  ];

  function onMenuClick(open: boolean) {
    console.log('menu open', open);
  }
</script>

<template>
  <div class="relative h-200 w-full">
    <BubbleMenu
      logo="/logo.svg"
      :items="items"
      animation-ease="back.out(1.5)"
      @menu-click="onMenuClick"
    />
  </div>
</template>
```

The menu is positioned `absolute` inside its nearest positioned parent. Set `use-fixed-position` to pin it to the viewport instead.

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        logo
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Image URL for the logo bubble. Use the <code>
        logo
      </code>
      
       slot for markup.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        menu-aria-label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Toggle menu'
      </code>
    </td>
    
    <td>
      Accessible label for the menu button.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        menu-bg
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#fff'
      </code>
    </td>
    
    <td>
      Background of the logo and menu bubbles, and the base pill color.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        menu-content-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#111'
      </code>
    </td>
    
    <td>
      Color of the menu icon lines and the pill text.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        use-fixed-position
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Positions the menu with <code>
        fixed
      </code>
      
       instead of <code>
        absolute
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        items
      </code>
    </td>
    
    <td>
      <code>
        BubbleMenuItem[]
      </code>
    </td>
    
    <td>
      5 sample links
    </td>
    
    <td>
      <code>
        label
      </code>
      
      , <code>
        href
      </code>
      
      , <code>
        ariaLabel?
      </code>
      
      , <code>
        rotation?
      </code>
      
      , <code>
        hoverStyles?: { bgColor?, textColor? }
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        animation-ease
      </code>
    </td>
    
    <td>
      <code>
        BubbleMenuEase
      </code>
    </td>
    
    <td>
      <code>
        'back.out(1.5)'
      </code>
    </td>
    
    <td>
      Bubble scale-in ease: <code>
        back.out(1.5)
      </code>
      
      , <code>
        power3.out
      </code>
      
      , <code>
        power2.out
      </code>
      
      , <code>
        elastic.out(1,0.5)
      </code>
      
      , <code>
        bounce.out
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        animation-duration
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      Duration of each bubble and label animation, in seconds.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stagger-delay
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.12
      </code>
    </td>
    
    <td>
      Base delay between bubbles, in seconds, with a slight random variance.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the nav wrapper.
    </td>
  </tr>
</tbody>
</table>

## Events / Slots

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

<tbody>
  <tr>
    <td>
      <code>
        menu-click
      </code>
    </td>
    
    <td>
      <code>
        open: boolean
      </code>
    </td>
    
    <td>
      Fired whenever the menu button is toggled.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        #logo
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Custom logo markup in place of the <code>
        logo
      </code>
      
       image.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The menu button exposes `aria-pressed` and a configurable `aria-label`. The links render as a `role="menu"` list of `menuitem` anchors with visible focus rings.
- Pills tilt only on viewports 900px and wider; on smaller screens they stack full width.
- With `prefers-reduced-motion: reduce`, the pills appear and disappear without scaling.
- Ported from [React Bits](https://reactbits.dev/components/bubble-menu) by David Haz.
