Product UI
Show a product through its real interface, and move each surface the way the real one moves.
A film about a product shows the product working, in its real interface: a capture, or a faithful copy built as a page component. A grey window with a word in a box is not a beat.
Why it exists
The rule show-real-thing says that each beat shows a real artifact working. A hand-written mock has a grey window, small type and a sparse grid. In a measured comparison, reference films filled 0.71 of the frame with picture, and a first mock filled 0.17.
The viewer also knows how ChatGPT, GitHub or a terminal behaves. A copy that moves differently reads as fake.
Choose the source of the screen
Take the first option that fits.
- Capture the real product. A capture of the real UI beats any mock.
bin/vawe shot <target> --tour <steps.json>drives a drawn cursor through a route or URL with real mouse events and writes an mp4. Add--scale 3for a sharp close-up. - Copy a real interface as a component. The Product moments group of the moves library has faithful copies:
terminal-run,approval-gate,model-picker,diff-card,ai-stream-response,agent-progress,command-palette-summonand more. Each copies one real surface with its real type, colours and behaviour. - Design an interface for an invented product. Build it as a real interface: real-looking data, and controls that do something. Do not make functionless chrome.
Build an invented interface
The design kitThe design kit is films/<name>/DESIGN.md and the kit/ folder: the palette, the type, the ground and the parts of one film. has a skill ladder with five rungs: builder, type, colour, depth and frame. The builder rung builds product UI as a real interface. The frame rung then removes web-page patterns such as a nav, a CTA button, a pill or an eyebrow. Use both: build the parts at the builder rung, and clean the frame at the frame rung. See Design kit.
Rules for the screen (taste/craft/show-dont-tell.md):
- One focal element for each screen: the prompt in an editor, the hero tile in a grid.
- Fill most of the frame. Use display-size type, never web-size type.
- Show real images whole. Set each cell to the aspect of its source, so
object-fit: coverdoes not crop. - Take colour from theme tokens, with one accent.
- Make every claim in the copy true.
- Check the laid-out page, not the source. Read the real box of each element against the frame and the safe margin.
A screen that is the subject of the film goes in an element with data-ui. Its text floor is 3 percent of the frame height as cap height, not 6 percent, because it is sized like a real screen. No text on a built screen is under 28 px at 1920 wide.
<div data-ui class="app"> ... </div>Move a surface the way the real one moves
A copy must move as the real surface moves. A product that animates nothing must not animate in the copy.
| surface | how it moves | move that shows it |
|---|---|---|
| terminal | Every character, row and swap lands in whole steps on one frame. No ease, no fade, no scale. | terminal-run, approval-gate |
| web page already loaded | The page is whole. State changes swap on one frame, such as a checks chip or a badge. | diff-card |
| chat | The prompt types at a steady rate. The answer streams in chunks of 1 to 3 words. | ai-stream-response |
| menu or dialog | A short fade and a small scale. The selection mark moves on one frame. | model-picker, command-palette-summon |
| window or panel | Fades in with at most a few pixels of rise. No scale. | any move that uses arrive |
Only the window arrives with an ease in a terminal clip, and that is clip framing, not the product. The text inside the window never eases.
No invented overshoot
Overshoot is a snap on a small, fast arrival of 3 to 20 px. A large panel does not overshoot. Do not add a spring or a bounce where the real interface has none. The overshoot-share check compares a whole film with reference films, so use overshoot for the arrival that matters and not for the interface. See Motion.
The measured numbers
core/motion/ui.js holds the timing of real product screens, in milliseconds, read from reference clips. core/motion/presets.js re-exports it as UI.
import { UI, EASE } from '../../core/motion/presets.js';| name | value | what it measured |
|---|---|---|
UI.typing | 30 ms a character, 70 ms more after a space, 25 percent jitter | 20 to 27 characters a second with pauses at word gaps |
UI.stream | 55 to 110 ms between chunks, 200 ms fade | 15 to 25 words a second |
UI.send | spring k 500, d 25 | a sent bubble overshoots 11 to 14 percent |
UI.panel | 220 ms fade, 0.8vh rise | a window fades in with no scale |
UI.row | 300 ms fade, 70 ms stagger | rows 70 to 100 ms apart |
UI.roll | 150 ms out, 260 ms in | a digit rolls in its own clipped slot |
UI.exit | 300 ms, a 100 ms empty end | the window fades to the empty ground; frame 0 is the same ground |
UI.snap | overshoot 0.09 to 0.2 | a snap on small, fast arrivals |
The presets are for surfaces that animate. A terminal steps, and only UI.typing and UI.panel apply to it. Seed the typing jitter with rng.
Build a product beat
Pick the source from the list above. For a real product, capture it with bin/vawe shot.
bin/vawe shot https://example.com/app --tour tour.json --w 1600 --scale 2 --out out/app-tour.mp4Pick a Product moments move that matches the surface. Open prompts/moves/<move>.md and read the paragraph that starts with “Use when”. It says which real surface the move copies.
Copy the snippet. Keep the type, colours and timing of the copy. Change only the content: the prompt text, the file names, the numbers.
Put the screen in one world with data-world, with the UI in a data-ui element. See Worlds.
Draft the beat and look at the frames. Check that the claim in the copy is true and that the click shows its result.
bin/vawe dev films/<name>/page.html --from 4 --to 9To film the screen as a physical object, with tilt, depth and bloom, give it to the lens. See Lens and effects.
Troubleshooting
| what you see | why | what to do |
|---|---|---|
| The screen looks like a web template: grey, small type, sparse. | A hand-written mock, with no builder rung. | Fill the frame, use display-size type, one focal element. Run the builder rung of the ladder. |
| The copy looks fake, but the layout is right. | It moves differently from the real surface. | Compare with the table above. Remove the ease from a terminal and the scale from a panel. |
| A click shows no result. | The next frames do not show what the click did. | Add the result state on the frame of the press. |
readable-text-size advice on UI labels. | The UI is the subject but has no data-ui. | Put the screen in a data-ui element, or raise the type. |
| A copied move has too much overshoot. | The move uses a spring for a large panel. | Use EASE.land for the panel and keep the overshoot for a small snap. |
Moves
Design kit
Lens and effects
UI demo
From the blogProduct launch video generator from a website URL · CSS animation library: 126 copyable video components