Mochi
tier 2 — classesThe agent's character: one rigged drawing with arms that turn at the shoulder, legs that bend and step, and a face with its own parts. An action eases Mochi into a pose, a mood sets the face, a tone picks the colours, and a perch puts it on a card, a divider, a field or a button — sitting on it, peeking over it, hanging from it. The app writes a span and three or four attributes; the drawing is the library's.
Putting it on a page
<SednaMochi /> once in the host page, beside
<SednaStateArt />, writes the drawing into the document
for the same reason the state illustrations are written there: a same-document
<use> is the only kind every engine colours from the page. Every
.mochi then references #sedna-mochi. The name is the app's
aria-label; say who the agent is and, where it matters, what it is doing.
<!-- Once, in the host page, beside SednaStateArt. -->
<body>
<SednaStateArt />
<SednaMochi />
…
</body>
<!-- Anywhere in the app. -->
<span class="mochi" data-action="wave" data-mood="happy" role="img" aria-label="Support agent, waving">
<svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg>
</span>
Playground
Change the attributes and watch the joints ease from one pose into the next. Mood
auto keeps the face the action brings.
<span class="mochi" data-perch="sit" data-action="idle" role="img" aria-label="Ops agent">
<svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg>
</span>
One Mochi
.mochi is 112px; --size sets any other. Without an attribute Mochi
stands, breathes and blinks.
Hi Alex, I am the support agent. Ask me anything about your orders.
<div class="sedna-row sedna-gap-3">
<span class="mochi" data-action="wave" data-mood="happy" role="img" aria-label="Support agent, waving"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<p style="margin: 0; max-width: 32ch">Hi Alex, I am the support agent. Ask me anything about your orders.</p>
</div>
Actions
data-action is what the agent is doing: idle, wave,
point, think, work, talk, listen,
celebrate, oops, sleep, surprised,
done and love. Each brings a pose, a face and sometimes a prop — a
thought bubble, a laptop, confetti. think, work, listen and
sleep are done sitting down.
Changing it eases the joints from the old pose into the new one rather than swapping
a picture, so set it as the agent's work moves on. talk opens the mouth by
--level, the number the app's audio code writes for voice, and
chatters gently without one.
idlewavepointthinkworktalklistencelebrateoopssleepsurpriseddonelove<div class="sedna-row-wrap sedna-gap-3" style="row-gap: var(--space-10)">
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="idle" role="img" aria-label="Ops agent, resting"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>idle</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="wave" role="img" aria-label="Ops agent, waving"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>wave</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="point" role="img" aria-label="Ops agent, pointing"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>point</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="think" role="img" aria-label="Ops agent, thinking"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>think</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="work" role="img" aria-label="Ops agent, working"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>work</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="talk" role="img" aria-label="Ops agent, talking"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>talk</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="listen" role="img" aria-label="Ops agent, listening"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>listen</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="celebrate" role="img" aria-label="Ops agent, celebrating"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>celebrate</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="oops" role="img" aria-label="Ops agent, stuck"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>oops</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="sleep" role="img" aria-label="Ops agent, asleep"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>sleep</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="surprised" role="img" aria-label="Ops agent, surprised"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>surprised</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="done" role="img" aria-label="Ops agent, done"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>done</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 120px">
<span class="mochi mochi--md" data-action="love" role="img" aria-label="Ops agent, pleased"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>love</code>
</div>
</div>
Moods
data-mood sets the face alone, over whatever the action chose: neutral,
happy, excited, surprised, curious,
focused, worried, sleepy, love and
wink. Leave it off and the action brings its own. Eyes, brows, mouth and cheeks
cross-fade.
neutralhappyexcitedsurprisedcuriousfocusedworriedsleepylovewink<div class="sedna-row-wrap sedna-gap-3" style="row-gap: var(--space-8)">
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="neutral" role="img" aria-label="Ops agent, neutral"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>neutral</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="happy" role="img" aria-label="Ops agent, happy"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>happy</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="excited" role="img" aria-label="Ops agent, excited"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>excited</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="surprised" role="img" aria-label="Ops agent, surprised"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>surprised</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="curious" role="img" aria-label="Ops agent, curious"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>curious</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="focused" role="img" aria-label="Ops agent, focused"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>focused</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="worried" role="img" aria-label="Ops agent, worried"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>worried</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="sleepy" role="img" aria-label="Ops agent, sleepy"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>sleepy</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="love" role="img" aria-label="Ops agent, love"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>love</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-mood="wink" role="img" aria-label="Ops agent, wink"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>wink</code>
</div>
</div>
Tones
data-tone picks the body, the limbs and a headset in a colour that goes with them:
pink, the default, mint, sky, lemon,
lilac and coral. Like an agent avatar's
tone, it is identity: give each agent one and keep it. Mochi keeps its colours under every theme
— the --mochi-* tokens on the Tokens page.
pinkmintskylemonlilaccoral<div class="sedna-row-wrap sedna-gap-3" style="row-gap: var(--space-8)">
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-action="listen" role="img" aria-label="Pink agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>pink</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-tone="mint" data-action="listen" role="img" aria-label="Mint agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>mint</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-tone="sky" data-action="listen" role="img" aria-label="Sky agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>sky</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-tone="lemon" data-action="listen" role="img" aria-label="Lemon agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>lemon</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-tone="lilac" data-action="listen" role="img" aria-label="Lilac agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>lilac</code>
</div>
<div class="sedna-col sedna-center sedna-gap-2" style="width: 96px">
<span class="mochi mochi--sm" data-tone="coral" data-action="listen" role="img" aria-label="Coral agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<code>coral</code>
</div>
</div>
Sizes, and a bust
.mochi--sm (64px), --md (88px), the bare .mochi (112px),
--lg (140px) and .mochi--xl (220px), for a page that is mostly Mochi.
.mochi--bust is head and shoulders in a circle of --size, for where an
avatar would go — the same markup, cut in CSS.
--sm--mdmochi--lg--xl--bust 28--bust 40--bust 56<div class="sedna-row-wrap sedna-gap-3" style="align-items: end">
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--sm" data-action="idle" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>--sm</code></div>
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--md" data-action="idle" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>--md</code></div>
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi" data-action="idle" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>mochi</code></div>
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--lg" data-action="idle" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>--lg</code></div>
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--xl" data-action="idle" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>--xl</code></div>
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--bust" data-action="idle" style="--size: 28px" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>--bust 28</code></div>
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--bust" data-action="idle" style="--size: 40px" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>--bust 40</code></div>
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--bust" data-action="idle" style="--size: 56px" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>--bust 56</code></div>
</div>
Holding still
.mochi--still keeps Mochi on the page without moving: no breath, no blink, no wave,
and every prop shown whole — the thought bubble stays up. It is the calm setting for an app
that lets the reader choose how lively the agent is. Pose, face and prop changes still ease in,
so keep setting data-action and data-mood as the work moves on.
Use the class rather than pausing the loops yourself. Out of view Sedna.UI.js holds
every Mochi's loops and lets them go again when it comes back; a still Mochi stays still either way.
Under prefers-reduced-motion every Mochi holds still and its changes land at once.
mochimochi--stillmochi--still<div class="sedna-row-wrap sedna-gap-3" style="align-items: end">
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--md" data-action="think" role="img" aria-label="Ops agent, thinking"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>mochi</code></div>
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--md mochi--still" data-action="think" role="img" aria-label="Ops agent, thinking"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>mochi--still</code></div>
<div class="sedna-col sedna-center sedna-gap-2"><span class="mochi mochi--md mochi--still" data-action="wave" data-mood="happy" role="img" aria-label="Ops agent, waving"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><code>mochi--still</code></div>
</div>
Perches
On a card
Mochi and the element it perches on share a .mochi-host; data-perch on the
Mochi says where. Each perch puts one line of the drawing on one edge of the element, so the seat,
the hands and the feet meet the edge exactly at any size: sit on the top edge,
peek and look over it, side from behind the end edge,
stand on the bottom edge and hang from it. What would be behind the
element is clipped away on the drawing's own grid, along the same line, and the hands that hold
the edge are drawn in front of it.
--perch-inset on the Mochi moves it along its edge — 24px from the end by
default. Leave room for it: a perch draws outside the element, and the host does not grow.
wave or point on peek, look or hang
lets go with one hand.
sit on the top edge, feet dangling in frontpeek over the top, both hands on the edgepeek and wave: one hand lets golook leaning in, reading what is belowside leaning out from behind the edgestand on the bottom edgehang from the bottom edge by both handshang and wave: one hand lets go<div class="sedna-row-wrap sedna-gap-3" style="align-items: start; row-gap: var(--space-9)">
<figure class="sedna-col sedna-gap-3" style="margin: 0; width: 260px; padding-block-start: 96px">
<div class="mochi-host">
<div class="card">
<div class="card-head"><strong>Order sync</strong><span class="badge badge--go">Healthy</span></div>
<div class="card-body">Three runs since 03:30, all on time.</div>
</div>
<span class="mochi" data-perch="sit" data-action="idle" role="img" aria-label="Ops agent, sitting on the card"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<figcaption class="text-muted text-sm"><code>sit</code> on the top edge, feet dangling in front</figcaption>
</figure>
<figure class="sedna-col sedna-gap-3" style="margin: 0; width: 260px; padding-block-start: 96px">
<div class="mochi-host">
<div class="card">
<div class="card-head"><strong>Ticket summary</strong><span class="badge badge-ai"><i class="ri-sparkling-2-line"></i><span>AI</span></span></div>
<div class="card-body">214 tickets, mostly one sync failure.</div>
</div>
<span class="mochi" data-perch="peek" data-mood="curious" role="img" aria-label="Research agent, peeking over the card"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<figcaption class="text-muted text-sm"><code>peek</code> over the top, both hands on the edge</figcaption>
</figure>
<figure class="sedna-col sedna-gap-3" style="margin: 0; width: 260px; padding-block-start: 96px">
<div class="mochi-host">
<div class="card">
<div class="card-head"><strong>Welcome back, Alex</strong></div>
<div class="card-body">Two approvals are waiting for you.</div>
</div>
<span class="mochi" data-perch="peek" data-action="wave" data-mood="happy" data-tone="mint" role="img" aria-label="Support agent, waving over the card"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<figcaption class="text-muted text-sm"><code>peek</code> and <code>wave</code>: one hand lets go</figcaption>
</figure>
<figure class="sedna-col sedna-gap-3" style="margin: 0; width: 260px; padding-block-start: 96px">
<div class="mochi-host">
<div class="card">
<div class="card-head"><strong>Runbook</strong></div>
<div class="card-body"><span class="skeleton skeleton-text" style="width: 92%"></span><span class="skeleton skeleton-text" style="width: 76%"></span></div>
</div>
<span class="mochi" data-perch="look" data-tone="sky" role="img" aria-label="Research agent, reading the card"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<figcaption class="text-muted text-sm"><code>look</code> leaning in, reading what is below</figcaption>
</figure>
<figure class="sedna-col sedna-gap-3" style="margin: 0; width: 260px; padding-block-start: 96px; padding-inline-end: 72px">
<div class="mochi-host">
<div class="card">
<div class="card-head"><strong>Release 4.2</strong><span class="badge badge--warn">Staged</span></div>
<div class="card-body">Waiting for your go-ahead.</div>
</div>
<span class="mochi" data-perch="side" data-mood="curious" data-tone="lilac" role="img" aria-label="Release agent, peeking out from behind the card"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<figcaption class="text-muted text-sm"><code>side</code> leaning out from behind the edge</figcaption>
</figure>
<figure class="sedna-col sedna-gap-3" style="margin: 0; width: 260px; padding-block-start: 96px">
<div class="mochi-host">
<div class="card">
<div class="card-head"><strong>Report ready</strong></div>
<div class="card-body" style="padding-block-end: 88px">214 tickets summarised in 1:12.</div>
</div>
<span class="mochi" data-perch="stand" data-action="done" data-tone="lemon" role="img" aria-label="Research agent, done"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<figcaption class="text-muted text-sm"><code>stand</code> on the bottom edge</figcaption>
</figure>
<figure class="sedna-col sedna-gap-3" style="margin: 0; width: 260px; padding-block-start: 24px">
<div class="mochi-host" style="margin-block-end: 104px">
<div class="card">
<div class="card-head"><strong>Disk usage</strong><span class="badge badge--danger">97%</span></div>
<div class="card-body">src-db-14 is nearly full.</div>
</div>
<span class="mochi" data-perch="hang" data-tone="coral" role="img" aria-label="Ops agent, hanging from the card"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<figcaption class="text-muted text-sm"><code>hang</code> from the bottom edge by both hands</figcaption>
</figure>
<figure class="sedna-col sedna-gap-3" style="margin: 0; width: 260px; padding-block-start: 24px">
<div class="mochi-host" style="margin-block-end: 104px">
<div class="card">
<div class="card-head"><strong>New in this release</strong></div>
<div class="card-body">Approvals now show who asked.</div>
</div>
<span class="mochi" data-perch="hang" data-action="wave" data-mood="excited" data-tone="sky" role="img" aria-label="Support agent, hanging from the card and waving"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<figcaption class="text-muted text-sm"><code>hang</code> and <code>wave</code>: one hand lets go</figcaption>
</figure>
</div>
On a divider
A .divider in a .mochi-host is a line to
sit on, stand on or hang from. The divider's margin moves to
the host, so the host's edges are the line itself, and on a .divider-labelled Mochi meets
the line through the middle of the label, not the label's edge.
A hairline has nothing to hide behind, so peek, look and
side are for a box: on a line they show half a Mochi cut off by thin air. Leave room
above a sitting Mochi and below a hanging one; the text beside a divider does not move for it.
Three runs since 03:30, all on time.
Two approvals are waiting for you.
The nightly export finished at 02:14.
src-db-14 is at 97% and growing.
<div class="sedna-col" style="max-width: 560px; padding-block: 72px 88px">
<p class="text-muted">Three runs since 03:30, all on time.</p>
<div class="mochi-host">
<hr class="divider" />
<span class="mochi mochi--md" data-perch="sit" data-action="idle" role="img" aria-label="Ops agent, sitting on the divider"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<p class="text-muted" style="padding-block-start: 64px">Two approvals are waiting for you.</p>
<div class="mochi-host">
<div class="divider-labelled">Earlier today</div>
<span class="mochi mochi--md" data-perch="stand" data-action="wave" data-tone="mint" role="img" aria-label="Support agent, standing on the divider and waving"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<p class="text-muted">The nightly export finished at 02:14.</p>
<div class="mochi-host">
<hr class="divider" />
<span class="mochi mochi--md" data-perch="hang" data-tone="sky" style="--perch-inset: 64px" role="img" aria-label="Research agent, hanging from the divider"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
<p class="text-muted" style="padding-inline-end: 160px">src-db-14 is at 97% and growing.</p>
</div>
On a field, a search box or a button
A host is whatever box it wraps, so every perch works on a field or a search box as it does on a card. Wrap the control alone, not its label, so the edges are the control's.
A button is inline, and a plain host is a block as wide as the row, so Mochi
would perch at the row's far end. .mochi-host--inline shrinks the host to the button
and centres Mochi on it; --perch-inset still moves it.
<div class="sedna-row-wrap sedna-gap-3" style="align-items: start; column-gap: var(--space-11); row-gap: var(--space-9); padding-block: 72px 80px">
<div class="form-field" style="width: 260px">
<label class="form-label" for="mochi-runbook">Runbook name</label>
<div class="mochi-host">
<input class="form-input" id="mochi-runbook" value="Restart the order sync" />
<span class="mochi mochi--md" data-perch="peek" data-mood="curious" data-tone="lilac" role="img" aria-label="Research agent, peeking over the field"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
</div>
<div style="width: 300px">
<div class="mochi-host">
<div class="search">
<i class="ri-search-line search-icon" aria-hidden="true"></i>
<input class="search-input" type="search" placeholder="Search runbooks" aria-label="Search runbooks" />
</div>
<span class="mochi mochi--md" data-perch="hang" data-action="point" data-tone="sky" role="img" aria-label="Research agent, hanging from the search box"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
</div>
<div class="mochi-host mochi-host--inline">
<button class="btn btn--go btn--solid" type="button">Approve the change</button>
<span class="mochi mochi--md" data-perch="sit" data-action="celebrate" data-tone="lemon" role="img" aria-label="Release agent, cheering on the button"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
</div>
Pointing at something
data-perch="point" stands Mochi beside whatever the app names as its anchor, with
its fingertip on the anchor's edge and centred on it. Give the target an
anchor-name and the Mochi the matching position-anchor; CSS anchor
positioning does the rest, with no script, and the finger follows the target if it moves.
<div class="mochi-host" style="padding-block: 48px; padding-inline-start: 140px">
<button class="btn btn--go btn--solid" type="button" style="anchor-name: --approve-change"><i class="ri-check-line"></i><span>Approve the change</span></button>
<span class="mochi" data-perch="point" data-action="point" data-mood="happy" data-tone="coral" style="--size: 104px; position-anchor: --approve-change" role="img" aria-label="Ops agent, pointing at Approve the change"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
Pointing from the other side
data-facing="start" turns Mochi round, to point from after the target instead of
before it. In a right-to-left page the two sides swap with everything else.
<div class="mochi-host" style="padding-block: 48px; padding-inline-end: 140px">
<button class="btn" type="button" style="anchor-name: --open-report"><i class="ri-file-chart-line"></i><span>Open the report</span></button>
<span class="mochi" data-perch="point" data-facing="start" data-action="point" data-tone="sky" style="--size: 104px; position-anchor: --open-report" role="img" aria-label="Research agent, pointing at Open the report"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
In context
In a conversation
A .mochi--bust takes the avatar's place in a chat. Set it to
talk on the message being spoken, and back to idle when it is done.
<div class="card" style="max-width: 460px">
<div class="card-head">
<span class="sedna-row sedna-gap-2"><span class="mochi mochi--bust" data-action="idle" style="--size: 28px" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span><strong>Ops agent</strong></span>
<span class="agent-status"><span class="orb" data-orb="thinking" role="img" aria-label="Thinking"></span><span class="agent-status-label text-shimmer">Thinking</span></span>
</div>
<div class="card-body">
<div class="chat" role="log" aria-live="polite" aria-label="Conversation with Ops agent">
<div class="chat-message chat-message--mine">
<div class="chat-body"><div class="chat-bubble">Is the sync healthy again?</div></div>
</div>
<div class="chat-message">
<span class="mochi mochi--bust" data-action="talk" style="--size: 32px" role="img" aria-label="Ops agent"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<div class="chat-body">
<div class="chat-meta"><span class="chat-author">Ops agent</span><time class="chat-time" datetime="2026-09-14T09:20">09:20</time></div>
<div class="chat-bubble">Yes. Three runs since 03:30, all on time.</div>
</div>
</div>
</div>
</div>
</div>
On an empty state
Mochi asleep on an empty state says the agent has nothing to do,
without a sentence about it. --perch-inset centres it.
Support agent is resting. A new ticket wakes it up.
<div class="mochi-host" style="max-width: 360px; margin-block-start: 80px">
<div class="card">
<div class="empty-state">
<span class="empty-state-title">Nothing in the queue</span>
<p>Support agent is resting. A new ticket wakes it up.</p>
<button class="btn btn--sm" type="button">Assign a ticket</button>
</div>
</div>
<span class="mochi" data-perch="sit" data-action="sleep" data-tone="lilac" style="--size: 96px; --perch-inset: calc(50% - 48px)" role="img" aria-label="Support agent, asleep"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
</div>
Beside a notice
One Mochi per row of a list of what the agents finished, got stuck on or are waiting for. The words carry the news; Mochi carries how it feels.
- Report ready214 tickets summarised in 1:12
- Refund failedThe payment service did not answer.
- ListeningSay what you need.
<div class="card" style="max-width: 460px">
<ul class="list">
<li><div class="list-row">
<span class="mochi mochi--bust" data-action="celebrate" style="--size: 44px" role="img" aria-label="Research agent, celebrating"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<span class="list-main"><span class="list-title">Report ready</span><span class="list-sub">214 tickets summarised in 1:12</span></span>
<span class="list-meta"><button class="btn btn--sm btn--brand btn--solid" type="button">Open</button></span>
</div></li>
<li><div class="list-row">
<span class="mochi mochi--bust" data-action="oops" data-tone="sky" style="--size: 44px" role="img" aria-label="Billing agent, stuck"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<span class="list-main"><span class="list-title">Refund failed</span><span class="list-sub">The payment service did not answer.</span></span>
<span class="list-meta"><button class="btn btn--sm" type="button">Retry</button></span>
</div></li>
<li><div class="list-row">
<span class="mochi mochi--bust" data-action="listen" data-tone="mint" style="--size: 44px" role="img" aria-label="Voice agent, listening"><svg viewBox="0 0 160 160" aria-hidden="true"><use href="#sedna-mochi" /></svg></span>
<span class="list-main"><span class="list-title">Listening</span><span class="list-sub">Say what you need.</span></span>
<span class="list-meta"><button class="btn btn--sm btn--icon btn-mic" type="button" data-state="listening" aria-label="Stop listening"><i class="ri-mic-fill"></i></button></span>
</div></li>
</ul>
</div>
Motion
Every pose change eases on the joints, and every action has a loop of its own — a wave, a bounce,
feet kicking while it hangs. Pose changes ride --motion-slow, so an app that slows or
stops its motion slows or stops them too. .mochi--still drops the loops and keeps the
eased changes. Under prefers-reduced-motion Mochi still changes pose, face and props, but
at once, and nothing loops.
A Mochi out of view costs nothing: Sedna.UI.js marks it data-mochi-away, and
it holds still and is not drawn until it comes back, so a page can show as many as it likes. At rest
a Mochi in view runs two loops, a blink and a breath; an action adds its own, and a prop's loop runs
only while the prop shows.