# Jelly Radio

> Radio group of chips where the chosen one swells and barges its neighbours outward.

A radio group drawn as a row of chips. Click a chip and it swells, stretching wide before tall like jelly, while its neighbours get shoved outward in a staggered ripple. Arrow keys move the choice and jump straight to it.

<demo-jelly-radio>



</demo-jelly-radio>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import JellyRadio from '~/components/ui/jelly-radio/JellyRadio.vue';
  import type { JellyRadioItem } from '~/components/ui/jelly-radio';

  const level = ref('Medium');
  const theme = ref('system');
  const themes: JellyRadioItem[] = [
    { value: 'light', label: 'Light', icon: 'lucide:sun' },
    { value: 'dark', label: 'Dark', icon: 'lucide:moon' },
    { value: 'system', label: 'System', icon: 'lucide:monitor' },
  ];
</script>

<template>
  <JellyRadio
    v-model="level"
    :items="['Off', 'Low', 'Medium', 'High', 'Max']"
  />
  <JellyRadio v-model="theme" :items="themes" size="lg" aria-label="Theme" />
</template>
```

Every chip runs three springs: one for its shift and one per scale axis. The horizontal scale is stiffer and bouncier than the vertical one, which starts 50 ms later scaled by `jelly`, so the chosen chip widens before it grows tall. Neighbours start `stagger` ms later per step away and soften their stiffness with distance.

## 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>
        JellyRadioItem[]
      </code>
    </td>
    
    <td>
      <code>
        ['Off', 'Low', 'Medium', 'High', 'Max']
      </code>
    </td>
    
    <td>
      The chips. A string is both value and label; objects take <code>
        value
      </code>
      
      , <code>
        label
      </code>
      
      , <code>
        icon
      </code>
      
       (Iconify name) and <code>
        disabled
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        model-value
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Controlled value (<code>
        v-model
      </code>
      
      ). Outside changes jump.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        default-value
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      first item
    </td>
    
    <td>
      Initial value when uncontrolled.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        chip-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'var(--muted)'
      </code>
    </td>
    
    <td>
      Surface of an unchosen chip.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        active-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'var(--foreground)'
      </code>
    </td>
    
    <td>
      Surface of the chosen chip.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        text-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'var(--foreground)'
      </code>
    </td>
    
    <td>
      Label of an unchosen chip, and the hover tone.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        active-text-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'var(--background)'
      </code>
    </td>
    
    <td>
      Label of the chosen chip.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        size
      </code>
    </td>
    
    <td>
      <code>
        'sm' | 'md' | 'lg'
      </code>
    </td>
    
    <td>
      <code>
        'md'
      </code>
    </td>
    
    <td>
      Chip height 28, 36 or 44 pixels. Large is the touch-first size.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        8
      </code>
    </td>
    
    <td>
      Rest spacing between chips in pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        18
      </code>
    </td>
    
    <td>
      Corner radius in pixels. 18 is a pill at medium.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        swell
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.2
      </code>
    </td>
    
    <td>
      How much the chosen chip grows. It also sets the room the neighbours make.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        barge
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        6
      </code>
    </td>
    
    <td>
      Extra pixels every neighbour is shoved beyond that room. 0 only makes room.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shrink
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.05
      </code>
    </td>
    
    <td>
      How much every unchosen chip gives up.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        jelly
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      The wide-before-tall split. 0 swells uniformly, 1.5 exaggerates it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bounce
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.25
      </code>
    </td>
    
    <td>
      One minus the damping ratio. 0 arrives and stops, 0.4 rings.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stagger
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        22
      </code>
    </td>
    
    <td>
      Milliseconds per row step before a neighbour moves. 0 moves the row as a slab.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stiffness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        580
      </code>
    </td>
    
    <td>
      Spring stiffness of the chosen chip. Neighbours soften with distance.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        disabled
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Fades the group and ignores input.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aria-label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Options'
      </code>
    </td>
    
    <td>
      Accessible name of the group.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        HTMLAttributes['class']
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Extra classes for the group.
    </td>
  </tr>
</tbody>
</table>

## Events

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

<tbody>
  <tr>
    <td>
      <code>
        update:modelValue
      </code>
    </td>
    
    <td>
      <code>
        (value: string)
      </code>
    </td>
    
    <td>
      Fired on the commit, before the motion ends.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        change
      </code>
    </td>
    
    <td>
      <code>
        (value: string, index: number)
      </code>
    </td>
    
    <td>
      Same commit, with the chip index.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- `role="radiogroup"` with `role="radio"` chips and `aria-checked`; only the chosen chip is in the tab order (roving tabindex).
- Arrow keys move the choice and skip disabled chips, wrapping at the ends. Home and End jump to the first and last enabled chip. Keyboard choices jump without the spring.
- Keyboard focus draws a ring around the chip.
- With `prefers-reduced-motion: reduce` every change jumps.
- Ported from [React Bits](https://reactbits.dev/micro/jelly-radio) by David Haz.
