Illustrations
Ready-made isometric scenes for the moments every product has, drawn from your theme. Install one on its own, draw it by id, or hand it to a block.

Put a scene in an empty state, an onboarding step or a hero, and it's drawn in your theme's colors, light or dark. This one assembles as it comes into view. Point at it and it lifts off.
Each illustration is a small isometric scene: plinths, boxes, cylinders, stacks, turned solids and extruded profiles, shaded from your tokens, with one part in the accent. They cover the moments a product is built around (where its data lives, the machines that serve it, the screens people spend the day in, shipping, safety and the states nobody plans for) and the things people use products for, from shopping and money to travel, food, pets and the weather.
They all frame the same volume, so one scene swaps for another without the layout moving. Draw one by id with <Illustration>, import it as its own component, or pass its id to a block's illustration prop. Each assembles as it comes into view, and comes apart on hover when you ask it to.
The illustrations explorer is the gallery: search every scene by name, category or tag, preview one live in any preset, light or dark, copy its code or its install command, and download it as SVG or PNG.
To make a scene of your own, use the illustration builder (experimental): drag the kit's parts into place, preview it assembling, and export code, SVG or PNG. Draw your own covers the same parts in code.
Installation
From the package
Every scene ships in @youj/illustrations. Draw one by id, or import its component:
npm i @youj/illustrationsimport { Illustration, IllustrationCoffee } from '@youj/illustrations'Then add the package's stylesheet to your global CSS, after Tailwind and the tokens. Its sheet points Tailwind at the scenes; without it the classes they use are never generated:
@import 'tailwindcss';
@import '@youj/react/styles.css';
@import '@youj/illustrations/styles.css';One scene, as source
To own and edit a single scene, copy it in with the Youj CLI in an app set up from source (npx @youj/cli init --mode registry). Every scene is its own item, illustration-<id>. It brings the kit the scenes are drawn with (illustration-kit) and the shapes it uses, and nothing else. In an app on the packages, the same command installs @youj/illustrations and adds its stylesheet instead:
npx @youj/cli add illustration-coffeeIt lands in components/youj/graphics/illustrations/scenes/<id>.tsx, and you import it from there through your @/ alias:
import { IllustrationCoffee } from '@/components/youj/graphics/illustrations/scenes/coffee'Each scene's id and install command are on its card in the explorer.
Every scene
A scene's id is what <Illustration scene> takes, and its component is Illustration and the id in PascalCase (server-rack is IllustrationServerRack). The ids, by category:
| Category | Scene ids |
|---|---|
| Data | backups database pipeline storage warehouse |
| Infrastructure | cloud edge load-balancer network server-rack |
| App | analytics calendar documents mail notifications search |
| Lifecycle | deploy empty not-found security success team |
| Commerce | delivery shopping-bag storefront |
| Finance | calculator coins invoice payment savings stock-chart wallet |
| Health | first-aid fitness heart-rate pills stethoscope tooth yoga |
| Education | abacus books globe graduation idea library school-backpack |
| Travel | flight map suitcase |
| Food | bakery cocktail coffee cooking groceries pizza restaurant |
| Media | camera design-tools easel film-reel guitar music video |
| Social | announcement chat community newsletter phone-call podcast reactions video-call |
| Home | gift home plant |
| Work | briefcase checklist trophy |
| Sports | basketball bicycle stopwatch tennis |
| Play | arcade dice game-controller puzzle |
| Events | cake party stage ticket |
| Pets | cat-tree dog-house fish-bowl pet-bowl |
| Weather | lightning rain snowflake sun-cloud |
| Nature | flower greenhouse island mountain recycling tree water-drop |
| Energy | battery ev-charger solar-panel wind-turbine |
| Transport | bus car scooter ship train |
| Places | bank hospital lighthouse museum office-tower school |
| Outdoors | beach binoculars compass hot-air-balloon tent |
| Science | atom dna lab-flask microscope telescope |
| Security | fingerprint key padlock shield vault |
| Devices | drone laptop phone printer smartwatch vr-headset |
| Business | contract handshake meeting org-chart presentation scales target |
npm i @youj/illustrationsFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
From the package, everything on this page comes from @youj/illustrations (pnpm add @youj/illustrations).
@import 'tailwindcss';
@import '@youj/react/styles.css'; /* or '@youj/core/styles.css' without the components */
@import '@youj/illustrations/styles.css';Its sheet points Tailwind at the built scenes, so it generates the classes they use; it goes after the tokens, in the same file. npx @youj/cli add adds it for you.
From the registry, the illustrations item copies every scene into @/components/youj/graphics/illustrations/, with the kit's shapes they're built from.
Usage
import { Illustration } from '@youj/illustrations'
export function NoInvoices() {
return <Illustration scene="documents" title="No invoices yet" />
}Each scene is also its own component, for when you know which one you want and would rather import it directly:
import { IllustrationDatabase } from '@youj/illustrations'
<IllustrationDatabase explode="hover" />An id that names no scene draws fallback instead, so an id you stored that outlives its scene still draws something:
import { Illustration } from '@youj/illustrations'
function Empty({ sceneId }: { sceneId: string }) {
return <Illustration scene={sceneId} fallback="empty" />
}Every scene frames one volume, so swapping never moves the layout. Keyed on the id, the new one assembles in place:
Colour
Scenes are gray by default, with the accent as the one coloured part. Turn colour on with colors="theme" and each scene's subject takes one soft hue, and a second hue sets apart a second kind of thing, such as raw and processed data in a pipeline or two people shaking hands. The structure stays neutral and the accent is still the one lit part.
<Illustration scene="pipeline" colors="theme" />The theme's pair is derived from the accent, so it follows every preset, light or dark:
- Hue 1 is the accent's hue at a muted chroma, the scene's main colour.
- Hue 2 is the hue opposite the accent, for the second kind of thing.
- Hue 3 is a quarter turn away. It is rare, for a third kind.
Hues are mixed toward the surface, so they always sit quieter than the accent.
colors also takes your own colours, in hue order: tokens, brand colours or oklch(). They're softened the same way, and an empty string or a missing entry keeps the theme's:
<Illustration scene="pipeline" colors={['var(--color-success)', 'var(--color-warning)']} />When you draw your own, give a shape hue={1} or hue={2} on top of its tone, and set colors on the scene. Without colors the shape shows its tone.
Examples
In a block
Blocks with room for a picture take an illustration prop: a scene's id, your own node, or null for the block's plainer look. Each opens on a scene that fits it.
<AuthShell variant="split" illustration="security">…</AuthShell>
<NoResults illustration="not-found" />
<MarketingCta illustration={<img src="/launch.svg" alt="" />} />
<NoProjects illustration={null} /> {/* the icon on its plates instead */}| Block | Default | null |
|---|---|---|
| App / Empty states | empty, search, security and cloud, one per state | The icon on its fanned plates |
| App / First source | Its own floor, with the chosen source waiting to land | No picture |
| App / Onboarding | team, on the welcome step | The sparkles icon |
| Auth / Layout and Login | deploy, in the split layout's art panel | The gradient and the quote alone |
| Marketing / CTA | deploy, beside the call to action from lg up | No picture |
On those blocks' pages, the preview's Illustration menu swaps the scene live. An id that names no scene falls back to the block's own.
Listing the scenes
illustrations lists every scene as { id, name, category, tags, Component }, in the explorer's order, for a picker or a gallery of your own. illustrationCategories lists the categories as { id, name }, in the order the explorer shows them. getIllustration(id) returns one entry, or undefined for an id that isn't registered, and isIllustrationId(value) says whether a string names a scene.
import { illustrationCategories, illustrations, isIllustrationId } from '@youj/illustrations'
const byCategory = illustrationCategories.map((category) => ({
...category,
scenes: illustrations.filter((scene) => scene.category === category.id),
}))
// A scene from a URL or a setting, narrowed to one that exists.
const scene = isIllustrationId(params.scene) ? params.scene : 'empty'Reference
Propstitle, description, assemble… (22)
Props
Every scene component, and <Illustration>, takes these.
| Prop | Type | |
|---|---|---|
title | stringNames the drawing for assistive tech; without it the scene is decorative (aria-hidden). | – |
description | stringA longer description, read after the title. | – |
assemble | booleanParts drop in, far ones first, the first time the scene comes into view. default true | true |
float | booleanThe scene's hero part bobs gently. default true | true |
explode | number | boolean | 'hover'Take it apart: a number from 0 to 1, a boolean, or 'hover' (stacks part and the lit part rises while the pointer is over it). default 0 | 0 |
unit | numberCSS px per grid unit at the drawing's natural size. default 22 | 22 |
grow | booleanScale up past the natural size to fill a wider box. default false | false |
colors | readonly string[] | 'theme'Colour for the scene's parts: 'theme' for the theme's pair (derived from the accent), or any CSS colours, its main colour first (an empty entry takes the theme's). Default off: gray, with the accent the only colour. | – |
className | string | – |
style | CSSProperties | – |
<Illustration> adds the scene to draw:
| Prop | Type | |
|---|---|---|
scene | IllustrationSlot | stringA scene by id, or a node drawn as given. | – |
fallback | IllustrationId'security''target''key''success''database''home''storage''warehouse''analytics''pipeline''backups''server-rack''cloud''network''load-balancer''edge''mail''newsletter''calendar''meeting''documents''search''notifications''deploy''ship''vault''team''not-found''empty''storefront''shopping-bag''delivery''payment''wallet''coins''savings''heart-rate''fitness''first-aid''books''library''graduation''school''idea''suitcase''flight''map''coffee''restaurant''groceries''camera''music''video''chat''video-call''laptop''announcement''tree''plant''gift''checklist''briefcase''trophy''bicycle''basketball''stopwatch''tennis''game-controller''arcade''dice''puzzle''party''ticket''stage''cake''dog-house''fish-bowl''pet-bowl''cat-tree''sun-cloud''rain''snowflake''lightning''mountain''island''beach''flower''recycling''greenhouse''water-drop''solar-panel''wind-turbine''battery''ev-charger''car''bus''train''scooter''office-tower''bank''hospital''museum''lighthouse''tent''compass''binoculars''hot-air-balloon''microscope''lab-flask''atom''telescope''dna''padlock''fingerprint''shield''phone''smartwatch''vr-headset''printer''drone''handshake''presentation''easel''org-chart''contract''scales''stock-chart''calculator''invoice''phone-call''podcast''community''reactions''pizza''bakery''cocktail''cooking''pills''stethoscope''tooth''yoga''globe''school-backpack''abacus''guitar''film-reel''design-tools'The scene drawn when | – |
title | stringNames the drawing for assistive tech; without it the scene is decorative (aria-hidden). | – |
description | stringA longer description, read after the title. | – |
assemble | booleanParts drop in, far ones first, the first time the scene comes into view. default true | true |
float | booleanThe scene's hero part bobs gently. default true | true |
explode | number | boolean | 'hover'Take it apart: a number from 0 to 1, a boolean, or 'hover' (stacks part and the lit part rises while the pointer is over it). default 0 | 0 |
unit | numberCSS px per grid unit at the drawing's natural size. default 22 | 22 |
grow | booleanScale up past the natural size to fill a wider box. default false | false |
colors | readonly string[] | 'theme'Colour for the scene's parts: 'theme' for the theme's pair (derived from the accent), or any CSS colours, its main colour first (an empty entry takes the theme's). Default off: gray, with the accent the only colour. | – |
className | string | – |
style | CSSProperties | – |
MotionAssemble, float, explode · reduced motion
Motion
Scenes move on their stage's shared springs and clock, so the Speed control and the theme's personality reach them.
| Moment | Tokens | ||
|---|---|---|---|
| Assembleparts drop in, far ones first, the first time the scene is seen | spring.settlestagger.loose0.45s, bounce 0.3 · 72ms step | 0.45s, bounce 0.372ms step | |
| Floatthe hero part bobs, out of step with its neighbors, paused offscreen | no token one bob per 10 × deliberate | one bob per 10 × deliberate | |
Explodeexplode="hover": stacks part and the lit part rises; a reversal carries on from where it is | spring.smooth0.36s, bounce 0 | 0.36s, bounce 0 |
Reduced motion
The finished scene, drawn once: nothing drops, nothing bobs, and a scene that's apart is shown apart.
Reduced motion
Scenes are drawn finished and still. Nothing drops in, nothing bobs, and with explode set they're shown apart.
AccessibilityARIA
Accessibility
- Scenes are decorative by default (
aria-hidden): the words beside them carry the meaning, as in an empty state. Give one atitle, and adescriptionif it needs one, when the picture itself says something, and it becomes a named image. - The accent marks one part, never the only difference: the lit part is also the one that floats, rises or is named nearby.
- Hues are decoration, not information: every part keeps its shape and its edges in any colour, and with colour off.
- Coming apart on hover is a flourish, not a control: it answers a mouse pointer only.