The Gryt Owls
Everyone on a Gryt server has an owl. It's drawn from their name, it's the same owl on every client, and anyone can draw a new hat for it.
Gryt has had an owl since 2023. It was the logo, it sat in the corner of the login page, and for a while it was a tiny astronaut in some promo art. It was never anybody in particular.
It is now. Everyone on a Gryt server who hasn't uploaded a picture is drawn as
an owl, worked out from their nickname, and the whole thing is a package called
@gryt/owl: give it a name, get an
SVG. No renderer, no DOM, no dependencies.
Why it isn't DiceBear
The obvious way to build this is DiceBear. It's good, it's free, and it has a dozen styles that would have taken an afternoon to wire up.
I tried it and took it out again, because DiceBear's model is a style definition made of interchangeable sprite layers, and that's not what I wanted. What I wanted was one drawn character with variations. The difference shows up in a member list: layered sprites read as a sticker sheet, and a single character with different colours and hats reads as one product.
So the bird never varies. The body, the wings, the face plate and the beak are fixed numbers on a 1024 artboard, and they're the same numbers for everyone. What the seed picks is the palette, whether there are ear tufts, and what the bird is wearing.
Fixed geometry is also what makes the accessories work at all. A pair of glasses is pinned to absolute coordinates, and it lands on the eyes because that's where the eyes are, on every owl, forever.
The same seed draws the same owl
That has to hold on every client and across every upgrade. A person is recognised by their avatar, so an owl that shifts when the library is bumped has failed at the one job it has.
Two consequences that look like fussiness and are not. Every random draw is keyed
on a name rather than a position, so adding a new hat doesn't reshuffle the
hats that were already there. And nothing depends on the platform: no
Math.random, no Date, no Intl, no DOM. The tsconfig drops the DOM library
on purpose, so reaching for document is a compile error instead of a crash on
somebody's phone.
Three seeds are pinned in the tests against their exact output. When one of those fails, the question to ask is whether every existing user is about to look different, not what the new value should be.
Thirty palettes
Ten hues in three schemes: night, day and dusk. The hues aren't picked by eye. They come from TILE_HUES, the list a voice tile's tint snaps to. So the colour
of your avatar and the colour of your tile in a call are the same colour rather
than two shades that nearly match.
An accessory names a colour role rather than carrying a hex value, which is why a teal owl and a pink owl can wear the same hat in their own colours.
Fifty drawings, and how they are stored
There are five slots — expression, glasses, head, neck and clothes — rolled independently, so a hat and glasses and a scarf can all turn up at once. A slot can also come up empty; empty is the most common outcome in every one of them. Right now there are fifty drawings across the five.
Adding one doesn't involve writing SVG path data or editing a manifest. You
export the bare bird, draw on top of it, and save the file with a name that says
what it is. Winter_Hat.svg is a hat because "hat" is in the filename.
Heart_Glasses.rare.svg is eyewear that turns up less often than the rest. The
filename is the configuration.
Two things travel with the drawing without being written down anywhere. Which of the bird's own layers you hid is which parts the accessory replaces, so a wink is one hidden eye and a coat is two hidden arms. And where you put the drawing relative to the bird's group says whether it's worn in front or behind. That's how a headset gets its band behind the head.
Colours aren't configuration either. One table maps the colours accessories are drawn in to palette roles, so a drawing that reuses colours already in use needs nothing at all. A colour nothing knows about stops the build and prints the line to add.
If you want to check a drawing before opening a pull request, there's a command that needs nothing but Node:
npx @gryt/owl check my-hat.svg
It tells you whether the filename gives it a slot, whether it's on the 1024 frame, whether the transforms are flattened, how much of the bird it found, and what's left once the bird is subtracted back out. It exits non-zero when the drawing wouldn't build, so it can hang off a commit hook.
Draw one
This is the bird you start from.
Most of the fifty were drawn in Figma against Build A Gryt, a community file that carries that bird as a component, with a layer per part and a walkthrough. Open it, duplicate it, draw a hat on the bird, export, and the filename does the rest.
If you're not in Figma, one command writes the same bird out as an SVG with the
same layers (Left Eye, Right Arm, Nose) and you can draw on that
instead.
The full walkthrough, with the bird to download and a worked example, is at ui.gryt.chat/avatars/drawing. The avatars page next to it draws every owl on it live, and counts how often each accessory turns up out of three thousand seeds, so the numbers on the page can't drift from the weights in the code.
Where the owls appear
The web client, the desktop app and the phone app all draw people from this same
file, so your owl is the same owl in all three. Voice tiles take their tint from
it. @gryt/ui's Avatar generates one when it has a seed and no image, and
@gryt/ui-native's does the same through react-native-svg.
Server icons are still DiceBear Planets. A server isn't a person and shouldn't be drawn as one.
A bare owl is about 2.1 kB of markup, and about 4.1 kB with accessories on.
Choosing your own owl
Everything above describes the owl your name happens to produce. As of this week you can also choose it, colours and expression and glasses and hat, from inside the client. That has its own post.
The package is MIT, like the rest of the UI packages and unlike the AGPL-3.0
apps that use them. bun add @gryt/owl and it'll draw owls for something that
isn't Gryt.