---
title: "BotAvatar"
description: "An animated bot avatar drawn on a canvas."
category: AiAgent
status: beta
since: 0.6.2
tags: [avatar, agent, bot, animation, canvas]
syncStatus: reviewed
verified: true
---

## Usage

### Basic
Drive `state` from the agent's real status: `working` while it streams or runs, `default` when idle.
:::TuffDemoWrapper{demo="BotAvatarShowcaseDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const working = ref(true)
  const TYPES = ['clover', 'flower', 'star', 'ghost', 'mech', 'circle', 'hexagon', 'square']
  </script>

  <template>
    <TxBotAvatar
      v-for="type in TYPES"
      :key="type"
      :type="type"
      :state="working ? 'working' : 'default'"
      :size="64"
    />

    <TxBotAvatar type="clover" :size="96" />
    <TxBotAvatar type="clover" :size="32" />
  </template>
---
:::

### Chat Reply

```vue
<template>
  <div class="msg msg--bot">
    <TxBotAvatar type="clover" :state="streaming ? 'working' : 'default'" :size="32" />
    <div class="msg-body">
      {{ streaming ? 'Thinking…' : content }}
    </div>
  </div>
</template>
```

### Agent Roster
Each instance offsets its blink timing by default, so a row never blinks in unison.

```vue
<template>
  <ul class="roster">
    <li v-for="agent in agents" :key="agent.id">
      <TxBotAvatar :type="agent.avatar" :state="agent.busy ? 'working' : 'default'" :size="32" aria-hidden />
      <span>{{ agent.name }}</span>
      <span class="muted">{{ agent.status }}</span>
    </li>
  </ul>
</template>
```

### Still Avatar

```vue
<template>
  <TxBotAvatar type="hexagon" :size="96" paused />
</template>
```

### Best Practices

- Set `size`; never put `width`, `height`, or `margin` in `style` or a class, or the avatar shifts or crops.
- Leave room above the avatar: the canvas draws 1.5× the `size` box, so a jump leaves the layout box and an `overflow: hidden` parent crops it.
- Map your own statuses: `running` / `streaming` / `pending` / `busy` → `working`; `idle` / `ready` / `online` → `default`; `offline` / `away` / `disabled` → `default` with `paused`.
- When the name and status already appear as text beside it, mark the avatar decorative with `aria-hidden`.
- Use it for bots only; give people a photo or initials.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `type` | `BotAvatarType` | `'clover'` | Body shape; each has its own palette color. |
| `face` | `BotAvatarFace` | type's own | Face kind. |
| `state` | `'default' \| 'working' \| 'sleeping'` | `'default'` | Idle (looks around, blinks, jumps now and then), working (hops, spins, smiles), or sleeping. |
| `size` | `number \| string` | `64` | Layout size; also accepts any CSS length. |
| `color` | `string` | type palette | Body color. |
| `ink` | `string` | auto | Face ink; dark by default, light on a dark body. |
| `brightness` / `saturation` | `number` | `1` / `1.5` | Lightness and vividness of the body color. |
| `speed` | `number` | `1` | Multiplier on every animation. |
| `paused` | `boolean` | `false` | Freezes the animation on its current frame. |
| `seed` | `number` | derived from instance | Offsets blink and glance timing so a row doesn't move in unison. |
| `shading` | `'plastic' \| 'crisp' \| 'smooth' \| 'flat' \| boolean` | `'plastic'` | How the body is lit; `true` means `crisp`, `false` means `flat`. |
| `shadow` / `highlight` | `number` | `0.35` / `1.3` | Strength of the shadow and lit sides. |
| `depth` | `number` | `0.65` | Body thickness, 0.2–2. |
| `light` | `number` | `265` | Light direction in degrees clockwise from the top. |
| `rim` | `number` | `0.5` | Lit rim width under `crisp`, Fresnel strength under `plastic`. |
| `spread` | `number` | `1.55` | Reach of the soft shading, or width of the highlight. |
| `interactive` | `boolean` | `true` | Eyes and head follow a nearby pointer; a click makes it hop and turn. |
| `theme` | `'auto' \| 'dark' \| 'light'` | `'auto'` | The surface beneath; `auto` reads an ancestor `data-theme` attribute or class, then the OS. |
| `turn` | `number` | `1` | How far the head turns side to side while idle. |
| `whirl` | `number` | `0` | Strength of the whirl ring around a spin. |
| `whirlSize` / `whirlWidth` / `whirlLength` / `whirlTilt` | `number` | `1` | Whirl ring geometry. |
| `jumpHeight` | `number` | `26` | Jump height in body units; the body is 100 tall. |
| `jumpTime` | `number` | `0.68` | Seconds a jump spends in the air. |
| `jumpStretch` / `jumpSquash` | `number` | `1` / `1.15` | Stretch in the air and squash on the ground. |
| `jumpSquashTime` / `jumpSquashEase` | `number` / `BotAvatarSquashEase` | `0.37` / `'pulse'` | Seconds and easing of the landing squash, from contact to recovery. |
| `jumpGroundTime` / `jumpGroundEase` | `number` / `BotAvatarSquashEase` | `0.11` / `'pulse'` | Seconds and easing of the hold at the deepest squash. |
| `jumpRiseTime` / `jumpRiseEase` | `number` / `BotAvatarSquashEase` | `0.33` / `'pulse'` | Seconds and easing of the rise from the deepest squash back to shape. |
| `jumpClickSquashTime` | `number` | `0.24` | Landing squash time for a click's jump. |
| `jumpSpin` | `number` | `1` | Whole turns made in the air. |
| `jumpLean` | `number` | `6` | Degrees of lean into a jump. |
| `jumpEvery` | `number` | `8` | Seconds between idle jumps, ±40%; `0` disables them. |
| `jumpLand` | `number` | `0` | When the landing squash starts relative to touch-down, in seconds. |

`BotAvatarType` has 18 shapes: `clover`, `flower`, `triangle`, `square`, `blob`, `ghost`, `circle`, `drop`, `star`, `droid`, `mech`, `alien`, `hexagon`, `cat`, `cloud`, `pill`, `pebble`, and `puddle`. `BotAvatarFace` is `'eyes' | 'mouth'`. `BotAvatarSquashEase` is `'sharp' | 'pulse' | 'soft' | 'bouncy'`.

### Exposed Methods

| Name | Type | Description |
|------|------|-------------|
| `canvas` | `HTMLCanvasElement \| null` | The root `<canvas>` element. |

## Overview

- The canvas is a `role="img"` whose `aria-label` follows the state ("Clover bot, idle"); pass `aria-label` to replace it.
- Other attributes and DOM listeners pass straight to the `<canvas>`, so `class`, `style`, and `data-*` work as usual.
- An unknown `state` falls back to `default`; an unknown `type` falls back to `clover`.
- One shared `requestAnimationFrame` loop serves every avatar; drawing stops offscreen and while the tab is hidden.
- Under reduced motion no loop starts and only the state's still pose is drawn; the preference is read at render time, not watched.
- State changes cross-animate, so switching `state` on every token or tool call is safe.

## Technologies

- Device pixel ratio is capped at 2; `plastic` shading bakes each body type on idle time when it first appears, with a softer look standing in until then.
- The drawing engine is a verbatim port of upstream [Jakubantalik/Libraries · bot-avatars](https://github.com/Jakubantalik/Libraries/tree/main/packages/bot-avatars) (MIT © Jakub Antalik); the Vue shell mirrors its React component.
- Source: `packages/tuffex/packages/components/src/bot-avatar/`.

<TuffDocSourceLink />
