Toggle Sidebar B
AppearanceLight & dark mode D

Companion

A small mascot that looks at the pointer: an animated bot with eighteen bodies, hats, glasses and finishes, or a blob with ten silhouettes and fourteen moods. It is a pet, not an avatar.

Playground

Every prop of the bot, live. Move the pointer around the stage and it follows. Pick Sleeping to see the resting pose, or turn on Clickable and press it.

Dark previewReplay previewOpen in new tab

Customize

Type
Finish
Face
Hat
Glasses
State
Body
Accessory
Size

Blob playground

The same for the blob. Hue, tone and shape are optional: left on Auto, the seed picks them.

Dark previewReplay previewOpen in new tab

Customize

Shape
Tone
Mood
Background
Hue
Size

Bodies

Eighteen bodies. Each has its own colour; the seed picks one when type is left out.

Dark previewReplay previewOpen in new tab
Clover
Flower
Triangle
Square
Blob
Ghost
Circle
Drop
Star
Droid
Mech
Alien
Hexagon
Cat
Cloud
Pill
Pebble
Puddle

Finishes

plastic is glossy and costs the most to draw. fabric is a plush fur. flat is a single fill, cheap enough for a grid of thumbnails.

Dark previewReplay previewOpen in new tab
Plastic
Fabric
Flat

Hats

Four hats, drawn over the body. Their colour comes from accessoryColor.

Dark previewReplay previewOpen in new tab
None
Beret
Beanie
Party
Crown

Glasses and faces

Three kinds of glasses, and the two faces: eyes or a smile.

Dark previewReplay previewOpen in new tab
None
Round
Square
Shades
Eyes
Smile

Accessories

Headphones and a bow tie, each with its own on/off. Both follow accessoryColor.

Dark previewReplay previewOpen in new tab
Headphones
Bow tie
Both
With a crown

Body colours

color overrides the body's own colour. Leave it empty to keep the type's.

Dark previewReplay previewOpen in new tab
Own
#41C4FF
#2FCB7A
#EFDB8E
#FF8A5B
#EE8BDB
#9A62FF
#EB7575
#F4F2FA
#3B4252

Sizes

size is any CSS length, so em scales with the text around it and rem or px pin it. The canvas overscans the box by 1.5x, so an inline companion does not push the line apart.

Dark previewReplay previewOpen in new tab
1.5rem
3rem
6rem
1.5rem
3rem
6rem

States

default looks around and blinks. sleeping closes its eyes and breathes. paused freezes whichever you picked on one frame.

Dark previewReplay previewOpen in new tab
Default
Sleeping
Paused
Not interactive

Interaction

clickable makes it a button: it hops and emits activate, and Enter and Space work. interactive controls the eyes following the pointer and the idle hop of the blob.

Dark previewReplay previewOpen in new tab

Press one, or Tab to it and hit Enter.

Inline in text

inline sits the companion in a line of text and pulls its overscan back so the line height does not change.

Dark previewReplay previewOpen in new tab

Good evening, Sam

The companion sits in the line and keeps the line height. Scale it with em and it follows the font size.

Blob shapes

Ten silhouettes. shape takes a value between 0 and 1; BLOB_SHAPES holds the mid-point of each band.

Dark previewReplay previewOpen in new tab
Round
Organic
Boxy
Capsule
Nub
Cloud
Droplet
Hexagon
Sun
Triangle

Blob hue and tone

hue is an angle from 0 to 359. tone runs from pale to ink.

Dark previewReplay previewOpen in new tab
8°
32°
52°
140°
175°
205°
235°
270°
310°
340°
Pastel
Pale
Mid
Deep
Bright
Ink

Blob moods

Fourteen expressions, set with expression.

Dark previewReplay previewOpen in new tab
idle
happy
sad
mad
surprised
wink
sleepy
smug
unsure
scared
love
shy
sick
thinking

Blob backgrounds

background puts the blob on a tile, or leaves it bare with none.

Dark previewReplay previewOpen in new tab
none
squircle
circle
square

Seed

The same seed always draws the same companion, so a user id or an email gives each person a stable one. Type below and watch it change.

Dark previewReplay previewOpen in new tab

From a config

Companion takes one CompanionConfig and picks the bot or the blob for you. defaultCompanion gives what a person who has not chosen gets. randomCompanion gives a full random one.

vue
<script setup>
import { Companion, defaultCompanion, randomCompanion } from "@/components/fx/companion";

const config = ref(defaultCompanion(user.email));
</script>

<template>
  <Companion :config="config" :seed="user.email" size="4rem" :inline="false" />
  <button @click="config = randomCompanion('bot')">Shuffle</button>
</template>
Dark previewReplay previewOpen in new tab
{
  "kind": "bot",
  "type": "triangle",
  "shading": "flat",
  "face": "eyes",
  "hat": "none",
  "glasses": "none",
  "headphones": false,
  "bowTie": true,
  "color": "#3B4252",
  "accessoryColor": "#27272b"
}

Credits

The bot engine is bot-avatars by Jakub Antalik (libraries.dev), MIT, vendored in fx/companion/engine with a few local patches listed in fx/companion/NOTICE.

The blob is blobatar and @blobatar/vue, MIT, installed as packages.

API Reference

Companion

PropTypeDefaultDescription
configCompanionConfigrequiredWhat to draw. config.kind picks the renderer: 'bot' or 'blobatar'. 'none' renders nothing.
seedstring'guest'The same string always draws the same companion where the config leaves a choice open.
sizestring'2.4em'A CSS length for the box.
inlinebooleantrueSits in a line of text. False makes it a block of size x size.
interactivebooleantrueEyes follow the pointer; the blob hops now and then.
stillbooleanfalseOne drawing, no loop. For thumbnails.
state'default' | 'sleeping''default'Bot only.
clickablebooleanfalseMakes it a button that hops and emits activate.
labelstring'Companion'The accessible name when clickable.
EventDescription
activateA click, or Enter or Space, while clickable.
readyThe companion has drawn its first frame.

CompanionBot

PropTypeDefaultDescription
typestring''One of BOT_TYPES. Empty lets the seed pick.
shading'plastic' | 'fabric' | 'flat''plastic'The finish.
face'eyes' | 'mouth' | ''''Empty keeps the type's own face.
hat'none' | 'beret' | 'beanie' | 'party' | 'crown''none'
glasses'none' | 'round' | 'square' | 'shades''none'
headphonesbooleanfalse
bowTiebooleanfalse
colorstring''Body colour as hex. Empty keeps the type's own.
accessoryColorstring'#27272b'Colour of the hat, headphones and bow tie.
seedstring'guest'Picks the type when it is empty, and the idle blink timing.
sizestring'2.4em'A CSS length for the body's box. The canvas overscans it by 1.5x.
state'default' | 'sleeping''default'
pausedbooleanfalseDraws one frame and stops the loop.
interactivebooleantrueEyes follow the pointer.
inlinebooleantrueSits in a line of text.
clickablebooleanfalseMakes it a button.
labelstring'Companion'The accessible name when clickable.
EventDescription
activateA click, or Enter or Space, while clickable.
readyThe first frame is on the canvas.

CompanionBlob

PropTypeDefaultDescription
seedstring'guest'The same string always draws the same blob. Hue, tone and shape pin parts of it.
huenumber | nullnull0 to 359. Null lets the seed choose.
tonenumber | nullnull0 (pale) to 1 (ink). Null lets the seed choose.
shapenumber | nullnullThe silhouette, 0 to 1. Null lets the seed choose.
expressionstring'idle'One of BLOB_EXPRESSIONS.
backgroundstring'none'A tile behind the blob, or 'none'.
sizestring'3.4em'A CSS length for the box. The blob fills about 60% of it.
inlinebooleantrueSits in a line of text.
interactivebooleantrueEyes follow the pointer, and it hops every ~16 s.
clickablebooleanfalseMakes it a button.
labelstring'Change companion'The accessible name when clickable.
EventDescription
activateA click, or Enter or Space, while clickable.
readyMounted.

Helpers

PropTypeDefaultDescription
BOT_TYPES, BOT_SHADINGS, BOT_HATS, BOT_GLASSES{ value, label }[]Every bot option, in display order.
BLOB_SHAPES, BLOB_TONES, BLOB_EXPRESSIONSarrayEvery blob option.
BODY_SWATCHES, ACCESSORY_SWATCHES, HUE_SWATCHESstring[] | number[]Colours the picker offers.
defaultCompanion(seed)CompanionConfigA bot whose body comes from the seed.
randomCompanion(kind)CompanionConfigA random, complete config for 'bot' or 'blobatar'.
normalizeCompanion(config)CompanionConfigDrops fields that do not belong to the config's kind.

Accessibility

Keyboard shortcuts and ARIA behavior.

Shortcut Description
EnterActivates a clickable companion.
SpaceActivates a clickable companion.
  • A companion that is not clickable is decoration: it is hidden from screen readers and ignores the pointer.
  • A clickable one is a button named by label.
  • With prefers-reduced-motion the hop is skipped. The eyes still follow the pointer.
  • The bot's canvas stops drawing when the tab is hidden.