Skip to content

Blueprint

Draft real UI as an architect's blueprint, with its measurements, then build it up stage by stage into the real thing.

Use it for an entrance that explains how a piece of UI is built: a hero demo, a product tour, a component on show. A draftsman lays out the frame, measures off its regions and pencils every part, while two columns beside it log the build and the values it applies. Then a ruled edge sweeps down and inks the real UI in, part by part. Keep it to one moment on a page: it asks to be watched, and a second one in view competes with the first.

Installation

npm i @youj/motion

import { BlueprintEntrance } from '@youj/motion'

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

Usage

Wrap the UI in BlueprintEntrance. It drafts its first child and plays on mount.

import { BlueprintEntrance } from '@youj/motion'

export function Hero() {
  return (
    <BlueprintEntrance>
      <SignInCard />
    </BlueprintEntrance>
  )
}

Change play to play it again (a counter works), or set it to false to show the UI as it is. onFinish is called when the run ends.

For an element you don't render with React, call the engine. It returns a cancel() and a finished promise, and an AbortSignal in signal cancels it too. To drive it from a clock of your own (a video rendered frame by frame, a scrubber), pass paused: true and call seek(t) with the time in ms from its start: it shows that moment and holds it. It only goes forwards, and a time at or past its end ends it.

import { blueprint } from '@youj/motion'

const run = blueprint(document.querySelector('#pricing')!, { speed: 1.5 })
await run.finished

Pacing

The build goes through seven stages, each advancing through the parts in build order, so it is visibly in progress from the first mark to the last: survey (the paper, the bounds and their dimensions), layout (the main regions), detail (every part in dotted pencil), ink (each outline traced over solid), tone (filled boxes in flat greys, hatching or a screen), set (the text sets in grey, line by line, and icons and charts trace their real shapes) and finish (a ruled edge sweeps down, and behind it the real colour, radii, shadows and images come in). It stays black and white until the finish.

The length grows with the demo: about 2 seconds for a card, about 3.5 for a small screen, and at most about 6 for a large one. Three options tune it:

  • duration fixes the whole run, in milliseconds.
  • speed scales it: 2 plays twice as fast. It applies on top of duration and the provider's speed.
  • phases shares the run out between the seven stages. The values are weights, so they are normalized and never change the total; a stage left out keeps its default ({ survey: 10, layout: 10, detail: 20, ink: 13, tone: 12, set: 13, finish: 22 }), and 0 skips it.
// a 2.4 s run with a quick pencil draft and a long, readable build-up
<BlueprintEntrance duration={2400} phases={{ survey: 6, layout: 6, detail: 12, ink: 18, tone: 18, set: 22, finish: 18 }}>
  <PricingCard />
</BlueprintEntrance>

A demo with no regions gives the layout's share to the detail.

The side columns show when there are about 180 pixels free on a side, inside the nearest container that clips; columns="none" leaves them out.

What it reads

The draft comes from the real DOM, read once before the first frame: roles, tags and data-slot first, then geometry. Boxes with a fill, a border or a shadow are drafted as dotted outlines, text as dotted baselines with a cap-height tick, icons as their square with a centre mark, charts as their axes, images as a crossed box, list rows as one line, and fields with a caret. The largest boxes become the numbered regions. Anything it can't classify, and open popovers, simply appear at the finish. Fills, borders and type are logged with the theme's token names, like fill surface and ink fg-muted.

Reference

Propsplay, duration, speed, phases, columns, onFinish

Props

BlueprintEntrance props
PropType
childrenRequiredReactNode

The UI to draft: its first element is the drawing's frame.

–
playboolean | number | string

Plays on mount and again whenever this changes; false shows the UI as it is.

default true

true
onFinish() => void

Called when a run ends, also early when focus moves into the UI (not when unmounting or a new play cancels it).

–
speednumber

Multiplies the pace: 2 plays twice as fast.

default 1

1
durationnumber

The whole run in ms, before speed. Default: from the demo's size and part count, about 2 s for a card, about 3.5 s for a small screen, at most about 6 s.

–
phasesBlueprintPhases

Each phase's share of the run, as weights (normalized, so they never change the total; a phase left out keeps its default).

default `{ survey: 10, layout: 10, detail: 20, ink: 13, tone: 12, set: 13, finish: 22 }`

`{ survey: 10, layout: 10, detail: 20, ink: 13, tone: 12, set: 13, finish: 22 }`
columns'auto' | 'none'

The side columns (build log and values), shown when there is room beside the demo.

default 'auto'

'auto'
Other props pass through: extends HTMLAttributes<HTMLDivElement>.

blueprint

function blueprint(node: HTMLElement, options?: BlueprintOptions): { cancel(): void; finished: Promise<void>; seek(t: number): void }
OptionTypeDefaultDescription
durationnumberfrom the demo's sizeThe whole run in ms, before speed.
speednumber1Multiplies the pace.
phasesBlueprintPhases{ survey: 10, layout: 10, detail: 20, ink: 13, tone: 12, set: 13, finish: 22 }Each stage's share of the run, as weights.
columns'auto' | 'none''auto'The build and values columns, when there is room.
labelsPartial<BlueprintLabels>Youj's labelsThe build column's words and the columns' titles. <BlueprintEntrance> takes them from <YoujProvider labels>.
signalAbortSignalAborting cancels the run.
reducedbooleanthe provider, else the OSSkip the draft.
personalityMotionPersonalitythe theme'sThe motion personality its timing and curves come from.
pausedbooleanfalseHold the run at its start until seek(t) moves it, for a clock of your own.
MotionSurvey, layout, detail, ink, tone, set, finish · reduced motion

Motion

Motion tokens
MomentTokens
Surveypaper grid, registration cross, dotted bounds, dimensions counting up
no token

10% of the run

10% of the run
Layoutthe main regions, outside-in, with index, size and corner
no token

10%

10%
Detailevery part pencilled in reading order, region by region
no token

20%

20%
Inkeach outline traced over in solid ink, part by part
no token

13%

13%
Tonefilled boxes take flat greys, hatching or a screen, top to bottom
no token

12%

12%
Settext sets in grey, line by line; icons and charts trace their shapes
ease.linear

cubic-bezier(0, 0, 1, 1) · 13%

cubic-bezier(0, 0, 1, 1)13%
Finisha ruled edge sweeps down at an even pace; the drawing clears in a soft band behind it
ease.linear

cubic-bezier(0, 0, 1, 1) · 22%

cubic-bezier(0, 0, 1, 1)22%
Piece inopacity, as the edge passes it: a card, a control, a row, a paragraph
duration.slowease.standard

340ms · cubic-bezier(0.2, 0, 0, 1)

340mscubic-bezier(0.2, 0, 0, 1)
Clearnumbers, columns and the paper fade
duration.slowease.exit

340ms · cubic-bezier(0.4, 0, 1, 1)

340mscubic-bezier(0.4, 0, 1, 1)

Reduced motion

No draft: the UI is just there, and nothing is added to the page.

The demo is never restyled. It is hidden and shown by Web Animations alone: an opacity hold while it's drafted, a clip-path wipe under the edge, and an opacity fade on each piece, created just before its moment so a screen only ever has a few in flight. The drawing lives on two canvases in an overlay beside the demo, and a frame only adds the marks due since the last one. When the run ends, or cancel() is called, the animations are cancelled and the overlay removed, so the page is exactly as it was. Focus or a press in the demo ends the run at once.

Reduced motion

The UI shows as it is, with no overlay and no animation. blueprint() returns a run that has already finished.

AccessibilityARIA, focus, pointer

Accessibility

  • The drawing and the columns are aria-hidden and never take the pointer. The demo stays in the accessibility tree the whole time, so a screen reader reads the real UI, not the drawing.
  • Tabbing into the demo, or pressing on it, ends the run at once, so nobody uses a part that hasn't been inked yet.
  • Nothing flashes: lines draw in pencil tones of the theme's foreground and the reveal is a slow sweep.