# Variable Proximity

> Letters change variable-font weight as the cursor gets close, and can shift color, grow and rise; an idle wave of weight runs while the cursor is away.

Text set in a variable font where each letter gets heavier the closer the cursor is. Move the pointer over the text to drag a bold wake through it. Letters can also blend toward an accent color, grow and rise under the cursor, and with the idle wave on, a slow ripple of weight runs across the text while the cursor is away. Lines are frozen after layout, so the text never rewraps while weights change.

<demo-variable-proximity>



</demo-variable-proximity>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import VariableProximity from '~/components/ui/variable-proximity/VariableProximity.vue';

  const containerRef = ref<HTMLElement | null>(null);
</script>

<template>
  <div ref="containerRef" class="relative">
    <VariableProximity
      label="Hover me! And then star nxui on GitHub, or else..."
      from-font-variation-settings="'wght' 300, 'opsz' 9"
      to-font-variation-settings="'wght' 800, 'opsz' 40"
      :container-ref="containerRef"
      :radius="150"
      falloff="gaussian"
      to-color="#ff5a36"
      :scale="1.22"
      :lift="0.16"
      :font-size="64"
    />
  </div>
</template>
```

The component loads [Roboto Flex](https://fonts.google.com/specimen/Roboto+Flex) from Google Fonts on mount and sets it on the root. Any variable font works: override the family with a `font-*` class and use axes that font supports.

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      The text to display
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        from-font-variation-settings
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        "'wght' 400, 'opsz' 9"
      </code>
    </td>
    
    <td>
      Variation settings letters rest at, away from the cursor
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        to-font-variation-settings
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        "'wght' 1000, 'opsz' 40"
      </code>
    </td>
    
    <td>
      Variation settings reached right under the cursor
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        container-ref
      </code>
    </td>
    
    <td>
      <code>
        HTMLElement | null
      </code>
    </td>
    
    <td>
      <code>
        null
      </code>
    </td>
    
    <td>
      Optional element around the text. Distance checks are skipped while the cursor is farther than <code>
        radius
      </code>
      
       from it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        50
      </code>
    </td>
    
    <td>
      Reach of the effect (px)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        falloff
      </code>
    </td>
    
    <td>
      <code>
        'linear' | 'exponential' | 'gaussian'
      </code>
    </td>
    
    <td>
      <code>
        'linear'
      </code>
    </td>
    
    <td>
      How the effect fades with distance
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        smoothing
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.2
      </code>
    </td>
    
    <td>
      How gently letters ease to new settings, from 0 (instant) to 1 (slow trailing wake)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        from-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Color letters rest at. Unset keeps the text color
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        to-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Color letters blend toward near the cursor. Unset keeps the text color
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scale
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Size a letter grows to under the cursor. 1 keeps the normal size
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        lift
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      How far (em) a letter under the cursor rises
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        idle-wave
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      A gentle wave of weight travels across the text while the cursor is away
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-size
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Font size. Numbers are px. Inherits the surrounding size when unset
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes on the root
    </td>
  </tr>
</tbody>
</table>

Other attributes (`style`, `@click`, …) pass through to the root `<span>`.

## Accessibility

- Each letter is `aria-hidden`; the full label is repeated in a visually hidden span so screen readers read it once as plain text.
- With `prefers-reduced-motion: reduce` the idle wave stays off. Proximity changes still follow the pointer, because they are direct feedback to it.
- The loop only runs while letters are settling or the idle wave is on, and the wave pauses while the text is off screen.
- Ported from [React Bits](https://reactbits.dev/text-animations/variable-proximity) by David Haz.
