Connection lost. Reconnecting… attempt 1 of 8
Paused. Your work is held on the server.
Could not reconnect.
This session has expired on the server.
An unhandled error has occurred.
Sedna.UI
v0.31.0 · main

Mochi

tier 2 — classes

The 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.

Order syncHealthy
Last run09:30
Duration4 min 12 s
Perch
Action
Mood
Tone
<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.

idle
wave
point
think
work
talk
listen
celebrate
oops
sleep
surprised
done
love
<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.

neutral
happy
excited
surprised
curious
focused
worried
sleepy
love
wink
<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.

pink
mint
sky
lemon
lilac
coral
<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
--md
mochi
--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.

mochi
mochi--still
mochi--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.

Order syncHealthy
Three runs since 03:30, all on time.
sit on the top edge, feet dangling in front
Ticket summaryAI
214 tickets, mostly one sync failure.
peek over the top, both hands on the edge
Welcome back, Alex
Two approvals are waiting for you.
peek and wave: one hand lets go
Runbook
look leaning in, reading what is below
Release 4.2Staged
Waiting for your go-ahead.
side leaning out from behind the edge
Report ready
214 tickets summarised in 1:12.
stand on the bottom edge
Disk usage97%
src-db-14 is nearly full.
hang from the bottom edge by both hands
New in this release
Approvals now show who asked.
hang 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.

Earlier today

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.

Ops agent Thinking
Is the sync healthy again?
Ops agent
Yes. Three runs since 03:30, all on time.
<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.

Nothing in the queue

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.