# Tech Text

> A canvas wordmark whose letters turn into dashed vector outlines under the cursor, with a gliding selection frame, draggable spring letters and click ripples.

A wordmark drawn on a canvas. Move the pointer over it and the letter under the cursor turns into its dashed outline inside a selection frame with blinking specks. Drag a letter off the baseline and it springs home; click a letter to kick it up and ripple its neighbours. With the pointer away, the reveal sweeps across the word on its own. Pick an intro to type, assemble or rule the font metrics on load.

<demo-tech-text>



</demo-tech-text>

## Installation

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

## Usage

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

<template>
  <div class="relative h-120 w-full text-foreground">
    <TechText
      text="nxui"
      :font-weight="600"
      reveal="letter"
      line-style="dashed"
      :specks="15"
      intro="assemble"
      :intro-duration="2.2"
    />
  </div>
</template>
```

The component fills its parent, so give the parent a height. Colours default to `currentColor`, so the wordmark follows the surrounding text colour in light and dark mode.

## 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>
      <code>
        'nxui'
      </code>
    </td>
    
    <td>
      The wordmark to render.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-family
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Any font family loaded on the page. Empty inherits the font of the container.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-weight
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        600
      </code>
    </td>
    
    <td>
      Weight of the wordmark.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        150
      </code>
    </td>
    
    <td>
      Largest font size in px. The wordmark shrinks to fit narrower containers.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        letter-spacing
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        -0.05
      </code>
    </td>
    
    <td>
      Extra space between letters, in em.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        align
      </code>
    </td>
    
    <td>
      <code>
        'left' | 'center' | 'right'
      </code>
    </td>
    
    <td>
      <code>
        'center'
      </code>
    </td>
    
    <td>
      Where the wordmark sits across the container.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'currentColor'
      </code>
    </td>
    
    <td>
      Colour of the letters and their outlines.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        accent-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'currentColor'
      </code>
    </td>
    
    <td>
      Colour of the selection frame, connector, specks and labels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reveal
      </code>
    </td>
    
    <td>
      <code>
        'area' | 'letter' | 'off'
      </code>
    </td>
    
    <td>
      <code>
        'letter'
      </code>
    </td>
    
    <td>
      <code>
        area
      </code>
      
       fades letters into outlines inside a circle around the pointer, <code>
        letter
      </code>
      
       outlines only the active letter, <code>
        off
      </code>
      
       keeps every letter solid.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reach
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        200
      </code>
    </td>
    
    <td>
      Radius of the area reveal, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        softness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.7
      </code>
    </td>
    
    <td>
      How gradually the fill hands over to the outlines. 0 is a hard edge.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dash-length
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        4
      </code>
    </td>
    
    <td>
      Length of each outline dash, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dash-gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      Space between outline dashes, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        line-style
      </code>
    </td>
    
    <td>
      <code>
        'dashed' | 'solid'
      </code>
    </td>
    
    <td>
      <code>
        'dashed'
      </code>
    </td>
    
    <td>
      How the outlines are drawn. Only the outer silhouette is traced.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stroke-width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.5
      </code>
    </td>
    
    <td>
      Thickness of the outline, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        specks
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        15
      </code>
    </td>
    
    <td>
      Squares that blink and respawn around the active letter, seeded per letter. 0 turns them off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        selection
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Frame the letter under the pointer. The frame glides from letter to letter.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        labels
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Label the frame with the letter and its size, or with its offset while dragged.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        draggable
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Let letters be dragged off the baseline. They spring back on release.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bounce
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.4
      </code>
    </td>
    
    <td>
      How much a released letter overshoots on its way home, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        click-kick
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Clicking a letter kicks it upward and sends a ripple through its neighbours.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        'none' | 'type' | 'assemble' | 'metrics'
      </code>
    </td>
    
    <td>
      <code>
        'none'
      </code>
    </td>
    
    <td>
      Entrance played once the wordmark is in view: <code>
        type
      </code>
      
       places each letter as an outline, <code>
        assemble
      </code>
      
       flies pixel specks into the letters, <code>
        metrics
      </code>
      
       rules the font lines and fills the letters zone by zone.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro-duration
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      Length of the intro, in seconds.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        guides
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Rule the baseline, x-height, cap height and descender while a letter is active.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sweep
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Sweep the reveal across the wordmark on its own while the pointer is away.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        speed
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Speed of the idle sweep.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes on the container.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The container has `role="img"` and uses `text` as its `aria-label`; the canvas is hidden from assistive tech.
- With `prefers-reduced-motion: reduce`, the intro, the idle sweep and the click ripple are skipped. Hover and drag still work.
- The animation loop only runs while the wordmark is on screen and something is moving.
- Ported from [React Bits](https://reactbits.dev/text-animations/tech-text) by David Haz.
