Docs

One component, <Mascot>. Everything below is a prop. Click any tile to copy it.

Install

Svelte 5 is the only peer dependency.

terminal
pnpm add mascbob
App.svelte
<script>  import { Mascot } from 'mascbob';  let mood = $state('idle');</script><Mascot {mood} theme="og" onboop={() => (mood = 'love')} />

Every list on this page is exported, so pickers you build stay in sync with the library:

import { MOODS, SHAPES, EYE_STYLES, ACCESSORIES, OUTFITS, SHOES, THEMES, REACTIONS } from 'mascbob';

Moods 17

mood sets eyes, lids, mouth, blush, effect and hand pose. Switching tweens every feature, so it morphs instead of cutting.

Colorways 13

Sneaker-style presets: a matte neutral body and one loud accent. Pass an object to override single colors on top of a preset.

<Mascot theme={{ base: 'noir', accent: '#ff4fd8', eye: '#00ffc6' }} />

Shapes 10

Eyes 9

Accessories 20

Combine as many as you like: accessories={['halo', 'glasses']}.

Outfits & shoes

The full figure (body, on by default) starts bare. Use body={false} for a square, head-only avatar.

Reactions 7

With interactive on, it reacts to the pointer. Reactions override mood only briefly.

follow
Leans toward the cursor.
pet
Stroke back and forth over its head. Emits { type: "pet", strokes }.
startle opt-in
Flick the cursor past it, fast. Emits { type: "startle", speed }.
dizzy
Circle around it twice. Emits { type: "dizzy", direction }.
shy opt-in
Get really close. Emits { type: "shy" }.
tickle
Boop it again and again. Emits { type: "tickle", level, boops }.
bored
Leave the mouse alone for 20 s. Emits bored, then wake when you come back.
<!-- defaults plus shy, without bored --><Mascot reactions={{ shy: true, bored: false }} /><!-- exactly these, and listen for them --><Mascot  reactions={['pet', 'startle']}  onreaction={(e) => console.log(e.type)}/>

Voice

Set mood="talking" and feed an amplitude in 0..1 into level. Without level, it babbles in fake syllables on its own. Stop the tracks and close the AudioContext when you're done.

Talk.svelte
<script>  import { Mascot } from 'mascbob';  let level = $state(0);  async function listen() {    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });    const ctx = new AudioContext();    const analyser = ctx.createAnalyser();    ctx.createMediaStreamSource(stream).connect(analyser);    const samples = new Float32Array(analyser.fftSize);    const tick = () => {      analyser.getFloatTimeDomainData(samples);      const rms = Math.sqrt(samples.reduce((sum, s) => sum + s * s, 0) / samples.length);      const target = Math.min(1, Math.max(0, (rms - 0.01) * 7));      // open fast, close slower, so the mouth doesn't flicker      level += (target - level) * (target > level ? 0.5 : 0.2);      requestAnimationFrame(tick);    };    tick();  }</script><button onclick={listen}>Talk</button><Mascot mood="talking" {level} />

Styling with CSS

Every color is a CSS variable. Set them on the mascot or any ancestor, and they win over theme.

--mascbob-body-light
Body highlight
--mascbob-body-mid
Body base color
--mascbob-body-dark
Body shade
--mascbob-visor
Ink: soles, outlines
--mascbob-eye
Face print
--mascbob-cheek
Blush
--mascbob-accent
Accent plate and gear
--mascbob-sprout
Sprout accessory
app.css
.brand {  --mascbob-body-light: …;  --mascbob-body-mid: …;  --mascbob-body-dark: …;  --mascbob-visor: …;  --mascbob-eye: …;  --mascbob-cheek: …;  --mascbob-accent: …;  --mascbob-sprout: …;}

Custom accessories

The accessory snippet draws your own SVG on top of the head, in the head's 200×200 coordinates. top is the crown's y and halfWidth its half width.

<Mascot>  {#snippet accessory({ top })}    <circle cx="100" cy={top - 10} r="8" fill="gold" />  {/snippet}</Mascot>

Accessibility & motion

  • With interactive it renders a real <button>, reachable by keyboard. Name it with label.
  • motion="auto" follows prefers-reduced-motion: loops stop and moods switch without tweening. Force either with full or reduced.
  • Mascots off screen pause their timers and animations, so a page full of them stays smooth.

All props

PropTypeDefault
moodMood

Facial expression, effect, hand pose and float speed. Changes morph smoothly.

'idle'
themeThemeName | { base?, ...colors }

A colorway preset, or a preset with individual colors overridden.

'og'
shapeShape

Head silhouette.

'capsule'
eyesEyeStyle

Eye shape. Every style morphs through all moods.

'round'
accessoriesAccessory[]

Any combination of the built-in accessories.

[]
bodyboolean

Full 2:3 figure with arms and legs. false draws a square head for avatars.

true
outfitOutfit

Clothing, only visible with body.

'none'
shoesShoes

Footwear, only visible with body.

'none'
buildBuild

Body proportions with body: chubby, lanky, chibi, or blob (no legs, bobs in place).

'standard'
handsboolean

Hands that gesture with the mood.

true
lookAt'pointer' | 'wander' | 'none' | { x, y }

Where the eyes go. { x, y } is a fixed direction in -1..1.

'pointer'
levelnumber

Mouth opening 0..1 while mood is talking, e.g. mic amplitude. Omit to animate on its own.

–
sizenumber | string

Width in pixels, or any CSS length.

160
floatboolean

Idle hover animation.

true
effectsboolean

Particles around the head: mood effects (sparkles, hearts, zzz) and boop bursts.

true
motion'auto' | 'full' | 'reduced'

auto follows prefers-reduced-motion.

'auto'
interactiveboolean

Renders a real <button> that squishes and fires onboop.

true
labelstring

Accessible name.

'Mascbob'
onboop() => void

Click, tap or keyboard press.

–
reactionsboolean | Reaction[] | { [reaction]: boolean }

Pointer reactions. true enables the defaults, a list enables exactly those, an object toggles single ones.

true
onreaction(event: ReactionEvent) => void

Fires when a reaction triggers.

–
accessorySnippet<[{ top, halfWidth }]>

Custom SVG on top of the head, in the head's 200×200 coordinates.

–
classstring

Class on the root element.

''

Made with Svelte 5 and a lot of boops. MIT licensed.