Moodstone: Animated AI Agent Avatars for React Native

Description:

Moodstone is a React Native avatar component that represents AI agent activity through animated facial expressions.

Procedural shapes, two eyes, and changing light form the avatar; nine moods and seven morphing silhouettes control its appearance and motion.

Change the mood as an agent thinks, streams, finishes, or fails. PNG snapshots and static or animated SVG exports extend the avatar beyond the native app.

Preview

How To Use It

Install Moodstone and dependencies:

npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets
npm install moodstone
Use it in your app
import { Moodstone } from "moodstone";
export function AgentAvatar() {
  return <Moodstone color="pine" mood="thinking" cut="hexagon" size={48} />;
}

Map Agent Activity to Moods

Change mood when the agent’s activity changes. A new mood restarts its animation loop. Shape and color remain independent choices.

MoodActivity
idleReady for input
observingReading context or awaiting a tool result
thinkingSearching or making a short tool call
processingReasoning for an extended period
workingStreaming output
doneFinished successfully
failedAn operation failed
invalidInput was rejected
inactiveOffline or paused

Shape, Color, and Playback

PropDefaultRole
moodidleActivity state from the mood table
cutcircleSilhouette preset: circle, squircle, square, diamond, hexagon, badge, and burst.
colorpineOne of 12 palette colors or a hex string
eyeColorwhiteWhite, black, or a hex color
size64Canvas size in dp
seedDEFAULT_SEEDThree numbers for light start angle, drift, and orbit
animatedtrueSet false for the mood’s still key pose
pausedfalseFreeze the current loop
phaseUnsetStill-frame position or loop start offset from 0 to 1
morphDuration460Shape-morph duration in milliseconds; 0 snaps
tuneUnsetCut-specific shape parameters
shapeUnsetPartial ShapeParams for a custom silhouette; mutually exclusive with tune
styleUnsetReact Native View style on the canvas
refUnsetMoodstoneHandle for snapshots, restarts, and resolved specifications

Use animated={false} for a static avatar in long lists. Keep a seed stable for an avatar’s lighting identity, or use randomSeed() when creating a new avatar.

useAvatarSpec(props) builds a resolved specification from component props when no rendered avatar ref is available.

Tune a Silhouette

tune accepts parameters specific to the cut. Hexagons expose sides, round, and rot; badges and bursts expose lobes, depth, and sharp.

<Moodstone
  cut="hexagon"
  tune={{ sides: 5 }}
  color="#4f8f79"
  mood="working"
  size={64}
/>

Capture a PNG

Use the ref handle to render an offscreen snapshot. snapshot accepts size (default 1024 pixels), optional background (transparent when omitted), and optional loop phase. It returns a Skia image or null; encode the image as a PNG string.

Ref methodPurpose
snapshot(options)Render an offscreen Skia image
restart()Replay the current mood
getSpec()Return the resolved avatar specification
import { useRef } from "react";
import { Button, View } from "react-native";
import { Moodstone, type MoodstoneHandle } from "moodstone";
export function ExportableAvatar() {
  const avatar = useRef<MoodstoneHandle>(null);
  function capture() {
    const image = avatar.current?.snapshot({
      size: 1024,
      background: "#0e1113",
    });
    const png = image?.encodeToBase64();
    if (png) console.log(png);
  }
  return (
    <View>
      <Moodstone ref={avatar} mood="done" color="sky" />
      <Button title="Capture avatar" onPress={capture} />
    </View>
  );
}

Generate Static or Animated SVG

The animated SVG uses SMIL. For tuned or custom shapes, populate the specification’s shape and run it through resolveSpec to fit the eyes.

import {
  DEFAULT_SEED,
  PALETTE,
  renderAvatarSvg,
  renderAnimatedAvatarSvg,
  stillFrame,
  type AvatarSpec,
} from "moodstone";
const spec: AvatarSpec = {
  seed: DEFAULT_SEED,
  color: PALETTE.pine,
  cut: "circle",
  mood: "thinking",
  eyeColor: "#FFFFFF",
};
const stillSvg = renderAvatarSvg(spec, stillFrame(spec), { size: 240 });
const animatedSvg = renderAnimatedAvatarSvg(spec, { size: 240, fps: 24 });

Alternatives and Related Resources

Tags:

Add Comment