moodidocs

moodi

Tiny 3D mood buddies for your UI.

A soft 3D character you drop into any page with one line. Its face shows what is happening. No models or textures to download; three.js is a peer dependency. One shared WebGL context for every buddy on the page, rendering pauses off-screen, and reduced motion is respected.

Install

npm i moodi three

Usage

React

import { Moodi } from 'moodi/react';

<Moodi shape="blob" mood={isLoading ? 'loading' : 'idle'} color="#8fb996" />

Web Component

<moodi-buddy shape="ghost" mood="happy" color="#6c5ce7" size="160"></moodi-buddy>
<script type="module">import 'moodi';</script>

CDN, no build step

<script type="module" src="https://esm.sh/moodi"></script>
<moodi-buddy mood="thinking"></moodi-buddy>

The React component is safe to render on the server. three.js is loaded lazily in the browser, so importing moodi/react in Next.js never runs WebGL code on the server.

Props

PropAttributeTypeDefaultDescription
shapeshape'ghost' | 'pebble' | 'blob' | 'drop' | 'bean' | 'cube''ghost'Body shape.
moodmoodMood'idle'Current state. Changes are interpolated, never instant.
colorcolorstring'#6c5ce7'Body color, any CSS color.
faceColorface-colorstringautoEyes and mouth. Auto-contrasts with the body.
cheekColorcheek-colorstringautoBlush. Derived from the body color.
sizesizenumber | string160px number or any CSS length.
speedspeednumber1Animation speed. 0 pauses.
followCursorfollow-cursorbooleantrueEyes, and slightly the body, follow the pointer.
interactiveinteractivebooleantrueHover lift and a little reaction on click.
shadowshadowbooleantrueSoft ground shadow.
soundsoundbooleanfalseA short sound effect per mood, and when it first appears.
settlesettlenumber0ms after a mood change when the body calms to gentle breathing; the expression stays. 0 = never.

Booleans on the Web Component take "true" / "false", e.g. sound="true".

React only

PropTypeDefaultDescription
posterstring—Image shown until the first 3D frame.
loading'idle' | 'interaction' | 'eager''idle'When three.js loads. 'interaction' waits for the first pointer, touch, key or scroll.
onMoodChange(detail) => void—Same as the moodchange event.
onReady() => void—The first 3D frame is on screen.

Sound

Off by default. Turn it on and every mood change plays a short sound effect, synthesized in the browser (no audio files). It also plays once when the buddy first appears. Browsers only allow audio after a click, tap or key press, so a sound requested before that waits a few seconds for the first one.

<Moodi mood="happy" sound />

<moodi-buddy mood="happy" sound="true"></moodi-buddy>

Settle

Some moods keep moving: happy hops, error shakes, love floats with hearts. With settle the buddy plays the mood for that many milliseconds, then its body calms down to gentle idle breathing and the extras fade out, while the face keeps the expression. Every new mood (or reaction) starts the timer again.

<Moodi mood={done ? 'happy' : 'idle'} settle={3000} />

<moodi-buddy mood="happy" settle="3000"></moodi-buddy>

Fast first paint

Give the buddy a still image and let three.js load on the first interaction. The page paints the image instantly and the live 3D buddy takes over without a visible swap.

<Moodi poster="/ghost-idle.webp" loading="interaction" />

<!-- Web Component: any child is shown until the first 3D frame -->
<moodi-buddy><img src="/ghost-idle.webp" alt="" /></moodi-buddy>

Moods

Shapes and colors

Shapes: Ghost, Pebble, Blob, Drop, Bean, Cube. Preset palettes are exported as PALETTES:

Iris #6c5ce7Peach #f2a07bSage #8fb996Sky #8ec5e8Lilac #b9a3e3Butter #f3d36bCoral #f07167Ink #2b2d42Cloud #ecebe8

Imperative API and events

const el = document.querySelector('moodi-buddy');
el.mood = 'thinking';
el.react('happy', { duration: 1200 }); // temporary reaction, then back to the current mood
el.sound = true;
el.addEventListener('moodchange', (e) => console.log(e.detail));
// e.detail: { mood, previous, reaction }
el.addEventListener('ready', () => {}); // first 3D frame drawn; the element also gets [ready]

In React, pass a ref to reach the element, or use onMoodChange and onReady.