# Circular Text

> Text laid out around a spinning ring that reacts to hover, can be flung with inertia, tilts toward the cursor in 3D and holds an icon in the middle.

Circular Text sets a line of text around a ring that keeps turning. Hover it and it eases into a new speed: slower, faster, paused or wildly fast. With `draggable` you can grab the ring and fling it; it coasts, then settles back to its cruising speed. It can lean toward the cursor in 3D, sit back in perspective like a planet's orbit, draw an inner circle, keep natural letter spacing with gaps between copies, and hold any content in the middle. Hover, drag and try the presets in the panel.

<demo-circular-text>



</demo-circular-text>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import CircularText from '~/components/ui/circular-text/CircularText.vue';
</script>

<template>
  <!-- "Stamp" preset -->
  <CircularText
    text="made with care • since 2026 • "
    color="#e5484d"
    on-hover="slowDown"
    direction="counterclockwise"
    :spin-duration="30"
    :hover-tilt="12"
    :size="220"
    :font-size="17"
    :font-weight="700"
    :letter-spacing="0.02"
    :repeat="2"
    :fill="false"
    ring
    draggable
  >
    <Icon name="lucide:star" class="size-12 fill-current" />
  </CircularText>
</template>
```

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        text
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Text laid out around the ring. End it with a separator for a seamless loop.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        spin-duration
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        20
      </code>
    </td>
    
    <td>
      Seconds for one full turn. <code>
        0
      </code>
      
       keeps the ring still until it is dragged.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        on-hover
      </code>
    </td>
    
    <td>
      <code>
        'slowDown' | 'speedUp' | 'pause' | 'goBonkers' | null
      </code>
    </td>
    
    <td>
      <code>
        'speedUp'
      </code>
    </td>
    
    <td>
      What happens while the pointer is over the ring. It eases in and out; <code>
        goBonkers
      </code>
      
       also shrinks it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        direction
      </code>
    </td>
    
    <td>
      <code>
        'clockwise' | 'counterclockwise'
      </code>
    </td>
    
    <td>
      <code>
        'clockwise'
      </code>
    </td>
    
    <td>
      Which way the ring turns.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        draggable
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Grab the ring and fling it. It coasts with inertia, then eases back to its cruise speed.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hover-tilt
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      How far the ring tilts in 3D toward the pointer, in degrees. <code>
        0
      </code>
      
       turns it off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tilt
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Leans the ring back in perspective, in degrees, so it orbits the center content like a planet ring.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        200
      </code>
    </td>
    
    <td>
      Diameter of the ring in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        24
      </code>
    </td>
    
    <td>
      Letter size in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-weight
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        900
      </code>
    </td>
    
    <td>
      Letter weight.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        letter-spacing
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Extra space between letters, in em. Applies when <code>
        fill
      </code>
      
       is off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Color of the letters and center content. Empty follows the surrounding text color.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        start-angle
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Where the first letter sits, in degrees clockwise from the top.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fill
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Spreads the letters evenly around the ring. Turn it off to keep natural spacing and leave a gap.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        repeat
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How many times the text repeats. With <code>
        fill
      </code>
      
       off, each copy gets its own gap.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ring
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Draws a thin circle just inside the letters.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ring-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Color of the inner circle. Empty matches the letters.
    </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>

## Slots

<table>
<thead>
  <tr>
    <th>
      Slot
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        default
      </code>
    </td>
    
    <td>
      Content shown in the middle of the ring, such as an icon or a short label. It tilts with the ring.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The ring is `role="img"` with the text as its `aria-label`, so screen readers read the phrase once instead of letter by letter.
- With `prefers-reduced-motion: reduce` the ring stays still, hover speed changes and tilt are off, and a drag does not fling.
- The spin loop stops while the ring is off screen.
- Ported from [React Bits](https://reactbits.dev/text-animations/circular-text) by David Haz.
