stageshow
Build faux product UI that looks like a real product, as a still illustration or an animated demo with a cursor. Browser and app windows, dashboards, phones, notifications, terminals, bento grids and cursor walkthroughs, framed, masked and cropped the way a marketing page, feature card or docs hero needs them. Use whenever someone wants a fake or mocked UI, a product demo or walkthrough animation, a hero or feature-card illustration of an app, a product shot in the style of a polished marketing site, or an animated cursor doing things in an interface, even if they never say "faux UI". React and plain CSS.
Install
npx skills add https://github.com/benjitaylor/skills --skill stageshowCraft
The rules for the still frame. Framing is in framing.md; everything that moves is in motion.md.
Index: Restraint, Type, Spacing and alignment, Icons and logos, Color and looks, Density, Chrome and devices, A page of demos, Recipes.
Restraint
- Count the real strings: the title, the thing acted on, what is typed, the result; usually 3 to 7. The rest is
Bar. - A word budget is not an element budget. A calendar needs a populated week, a search a collection, a filter enough rows.
- Lists are made of
Row; hand-built rows are where lists go wrong (examples/gmail). - Bars are the product's filler; the subject is words. Bars where the eye lands read unfinished (
examples/spotify). - In a still the subject is real, words and values; a sidebar or list of bars beside it is filler and fine.
- Where the story touches a list, two or three real rows and at most as many bars. Film flags
skeleton ladderat realistic fidelity only. - In a small surface a row is one line: in a popover, a card or a tile, avatar, name, value. No gray bar under a real name there; filler beside real words reads as noise.
- Named avatars take their own color (the kit default); gray discs are filler only.
- Label and value match: both words or both bars. A blank pill beside a real control is dead; leave it out.
- Keep the domain's signature content: album art, thumbnails, avatars; swapped for the feature's signal it is a generic list (
examples/dark-bento). - When the output is the subject (a log, a diff, a terminal), real lines are the content: short and few, the rest of the screen quiet.
- A terminal is a character grid: a spinner and a text tick share one
1chcell; each line owns its mark, swapped in place (a tick on its own line inexamples/ruled-bento). - One number gets to be big; the rest are lines (
examples/subscriptions). - Placeholder widths vary row to row (60, 45, 70, 52).
- A date-range band runs week by week with air between rows, fades where the range continues, and is capped only on the real start and end days, each a solid disc (a circle, not a rounded square) in the accent with the day in white.
- Say it once. One clear link for the feature, not its signal in header, satellite and every row; a second confirmation only when it differs (
examples/dark-bento). - No masthead for a fictional name: no monogram, logo, sidebar and nav in every crop; a real mark only when recognition needs it.
- The product's name appears only when the brief leans on it and the real product would show it there: a notification's app name, the app's own title bar. A brief that just names the product doesn't need it on screen; the page around the demo already says it.
- Simplify before polishing: the fewest surfaces and strings that explain the product, then borders and badges only where earned.
- Simplification keeps the character. Cut repeated framing before meaningful imagery; an empty white card is not the cure.
- Judge the silhouette before the details. At thumbnail size the subject, support and empty space make one picture.
- Compose objects, not an inventory of cards: hierarchy from proportion, space, material and ground, not more boxes (
examples/story). - The heaviest marks sit on what the story is about; never a 2px black underline under a gray bar.
- Space follows the story. The payoff gets the room; no empty band mid-window while the result is pushed into the crop. No gap above a card, no cream under a phone's last row, no void between a stat's label and its number.
- Dots, halos, badges and focus rings sit fully inside the content edge.
- Check imagery at its smallest size and over every surface it crosses; a pale thumbnail can look see-through.
- A specimen still describes one plausible app: title, toolbar, navigation and body agree.
- Nothing outsized. The hero element sits in proportion to a real screen.
- Realism is the developer's call. At
fidelity="realistic"or when asked, plausible content everywhere, still with room to breathe (examples/wikipedia). - Character is real too. A styled brief gets its colors, radii, mascot and motion in full, still a shippable product under these rules.
Type
- Fit the text before fixing the box. A control's border-box holds content, padding and border; flex centering does not make it fit.
- Small text gets a line height of at least 1.3 and insets on every side; check the text slot's own clearance.
- Real sizes, then scale: 11 to 15px UI text, scaled by the stage; never 7px type.
- One family: the product's or the system stack. Monospace only for code, identifiers and aligned numbers, never a technical product's labels (
examples/ruled-bento). - Keep only the text needed to recognize the product and follow the beat; extra logs and timestamps are noise.
- Sentence case unless the real product has caps; hierarchy from spacing and one weight step.
- No real headings. A faux app's title is a
div, neverh1toh6: the demo lives in someone's page outline.
Spacing and alignment
- A button keeps 8 to 12px from the field, line or card edge above it; sitting on the field it reads as touching (film flags
tight control), and much more lets it drift off from the field it belongs to. A floating card (a comment popup, a toast) pads the same all round, measured to the avatar's and button's edges, not their words: 20px above the name with 14px at the side reads as a miss. A composer's Post or Send sits close under its box; padding sized for the posted card goes on the card, not between box and button. - Words keep a 12px inset from the edge of a window, card, panel or hovered row; film flags
tight insetunder 8px. Bars may bleed, words do not. - Row padding is 12px, 8px for a dense list; a one-line row three times taller than its words is a
fat row. - A title over its subtitle reads as one item: 4 to 6px of ink between the lines, a 1.3 line height plus a 2px gap. The air comes from the row's padding (
examples/gmail). - Bars sit in the same line box as the text they replace, so a skeleton row keeps the real row's pitch.
- Bars keep their lines' white space, not line pitch: 6 and 5px bars want 7px between; a trailing time sits on line one.
- Text never touches a neighboring box: a fixed
gapof 8 to 12px, never leftoverspace-between(touching). - A placeholder bar follows the text rules: it never touches or runs under an avatar, badge, icon or bar.
- Center a label on its letters, not its line box. Capitals sit low in a plain line box, so a label beside an icon (a check and "Sent" in a button), in a pill, or an initial in a disc reads low. Trim the label:
text-box: trim-both cap alphabetic(the kit'sBadgeandAvataralready do). - Nested shapes pad evenly (a pill inside a field, an icon inside a button: the same distance from every edge), and the outer radius equals the inner radius plus that padding.
- Carry the inset round the bottom; a comfortable top does not excuse a cramped action.
- Content clears the bar above it: a page's first heading sits at least the content's side inset below a toolbar or title bar's rule, never just under it.
- Whitespace groups: a title sits closer to its metadata than to the next record; never spread lines equally to fill a frame.
- Align the reading edge, then the visible ink. A small icon aligns with the title's first line; size each lane before nudging.
- Center on the visible face. Take tracking back with a negative
margin-right; a pair centers as a pair. - Field text centers cap height to baseline (
text-box: trim-both cap alphabetic), as the kit'sFielddoes (examples/controls). - A popup under a field draws its ring inset so its sides line up with the field's.
- A control hugs its content; never
flex: 1with its contents at one end (lopsided). - Fix overflow by fitting the content, not by widening the container; re-measure padding after (
uneven). - Columns line up: every row shares one grid template with fixed tracks; optional content keeps its track empty; bars vary inside their track.
- A dot, badge or ring is not a column: in the gutter or on the avatar, 6px clear, inside the row's inset (
tight mark,examples/gmail). - No near-tangents. Edges 1 to 3px apart read as an accident: line them up exactly, or part them by at least the gutter and never under 8px.
- Don't end just short of an edge: real room, or move it down and crop it (
framing.md, rule 11). - A centered amount centers its digits;
$45centered as one string sits right of center to the eye. The currency sign hangs off the left, outside the centering: the digits are the centered box and the sign is absolutely placed atright: 100%inside it. A rolling amount rolls only the digits. - Links have no hover surface; a control gets a surface visible at rest.
- Hover and selection fills are inset and rounded; negative margins go on the list, not the row; copy a real product's square fills (
examples/wikipedia). - Clone a real product's menu from its computed styles (
examples/wikipedia). - A trigger with its popover open keeps its pressed look:
data-open, styled like[data-hover](examples/hero-demo). - A progress ring is one track and one arc, round caps, never butt-capped dashes chopped into segments; a week of days goes in the dots beside it. A number in a ring centers on its digits:
Rollwith proportional figures, notCount, whose fixed-width digits leave a 1 off center; trim it to cap height (text-box: trim-both cap alphabetic) and set an icon above it apart, so the figure the eye lands on is the ring's center, not the pair's box.
Icons and logos
- Imagery earns its place: real imagery or a good icon, never invented illustration; gray art only for empty states.
- Icons come from a set, never from your hand. The product's own pack first (its dependencies, icons folder, sprite or
Iconcomponent); otherwise a set chosen on purpose (below), used at one size and one stroke width throughout. A glyph drawn by hand is the first tell of generated UI: its weight never matches its neighbors (film flagsmixed icons). - No hand-drawn marks (pin, cursor, badge, ringed dot, chip, contactless arcs): from the kit or the product's assets, else leave them out.
- A drawn mark gets a close-up beside the real thing, seen as a stranger would.
- Otherwise choose a set on purpose: kit
Icon filledfor Apple, Material Symbols for Android, else Phosphor, Iconoir, Radix, Tabler or Heroicons; Lucide deliberately, never reflexively. - Apple chrome uses SF-style glyphs; a Lucide glyph only where no system glyph exists, as
examples/finderdoes. - One family, one stroke weight, one size (14 to 16px) per scene; equal boxes can still hide a heavy fill beside a thin outline.
- Even glyphs in even boxes; an odd icon in an even box snaps when motion ends (
half-pixel glyph). - Fixed boxes don't shrink: an icon button or chip in a flex row gets
flex: none(squeezed). - Small icons are filled. At 14px and under, and in tinted tiles, use the filled style; thin outlines go wiry (
examples/kit-extras). - Icons are one opaque color (
--ss-icon,--ss-icon-faint, opaque text tokens); fade withopacityon the svg; no duotone. - Emoji are content, never icons. A playful look rounds the icons, it does not swap them for emoji (
emoji icon). - Other products' logos come from
BrandMarkor a project asset, in original proportions and colors; never a look-alike (brands.md). - Center glyphs by eye: shift a play triangle toward its point by a sixth of its width.
- AI features get no sparkle, wand or star, and no purple as the AI color. Show what the AI does. The product's own icon is fine even when it is a sparkle (
examples/hero-demouses its product'slist-sparkle); a sparkle as generic AI decoration is not. - No invented logo. A letter or glyph in a colored square is not the product's mark (film flags
invented mark). Use the product's real mark, or a plain text wordmark.
Color and looks
- One neutral family for window, sidebar and cards; a colored ground or another material differs on purpose.
- Five inks and one accent to start; status colors mean status; an expressive product may need more, with a clear hierarchy.
- No redundant tonal borders. Try the fill alone; add the border for separation, state, or because the product draws it.
- Quiet is not faint: check text, actions and surface against the real ground.
- A gradient or glow serves the story or goes.
- The hero isn't outlined to point at it. An accent ring with a soft halo around the card the story is about reads as a focus state nobody gave it, and a dot beside its label is a mark with no meaning. Lift it the way the product would: its own surface, the raised shadow, a label in the accent. A ring is for a field that really has focus.
- A filled button keeps its color under the pointer: one step of its own color, never a gray swap; success is the one real change.
- In mono, mark the thing acted on with a solid step in surface and weight, never a translucent wash (
examples/dark-bento). - Nothing translucent overlaps. Overlapping avatars are
Facepile; stacked chips or cards take opaque fills (onion skin,examples/figma). - On dark themes depth comes from a lighter surface, not shadow; satellites go on
--ss-raised(examples/linear). - A light document over a dark workspace stays a readable light document with its own foreground and edge.
- The light inks are tuned for white cards on gray canvas; on white, step them up a tone.
- Status color stays legible: success, pending and disabled distinct, never passing through mud.
- Contrast has a purpose. A dark editor can be identity with a clear edge; a near-black strip for no reason is a hole.
- Grounds support the subject; a muddy halo standing in for foreground contrast does not (
backgrounds.md). - Build once, check every look:
theme(light, dark, blueprint, glass),palette(accent, mono),fidelity(minimal, standard, realistic);Textfor words, tokens for color. - A light pass does not prove dark. Film every look the request names;
--darkfilms the dark theme;theme="dark"is always dark.
Density
- Density follows the story. Would a person do this here? No element quota; empty space can be hierarchy; enlarge content before adding anatomy.
- Never under about 320 wide for a demo; a bento tile or a satellite card may be smaller. Below that, keep the action and readable type, drop the desktop anatomy.
Chrome and devices
- A messaging app on an iPhone keeps Messages' conventions unless the brief gives it a look: blue sent bubbles (
--ss-accentas the kit sets it), gray received, the plus and the mic in the composer, reactions on the bubble. "Our messaging app" names the product, not a palette; inventing an accent and a composer makes it read as a fake (examples/messages). - Does the frame help? Browser chrome only when the browser is part of the picture; a desktop app gets its real chrome (
examples/chrome). - Chrome for the task: a sharing panel needs a document, permissions and an action; a browser bar explains nothing.
- Columns run the full height of their window (
Window sidebar); a column that stops short leaves a seam (examples/finder). - Window chrome is fixed at 38px;
barSize="tall"(64px) only for a real icon-and-label toolbar (examples/finder-glass). - Never draw platform chrome from memory; open the real app or a screenshot first.
- macOS 26 Finder: solid white window, only the floating sidebar is glass, gray glyphs, selected row on a gray pill (
examples/finder-glass). - Quiet browser windows center their content and carry a rounded, centered URL pill (
examples/static-card). - A document that is not the hero is content: a ruled page with real margins, not another window.
- Phones come from the kit (the frames are in
kit.md, Phone);photofalls back topro, never an empty bezel (examples/phones). - System surfaces keep their real proportions. Never shrink the iOS clock; the payoff wins by content, timing and crop (
examples/dynamic-island). - A notification is one bold title over its message with
Notification's geometry and a real app icon (examples/phones). - Nothing sits under the island; a sheet or grabber starts below the status bar.
- The status bar and home indicator come from the Phone, true to iOS, never drawn by hand. A second indicator inside the app shows as a bar that scales into place and a sliver under the real one. An app that covers a dark screen turns the Phone's
darkoff as its surface arrives, and both fade to dark. - A phone is lit from its first frame: never dimmed, blacked out or faded up.
- A phone background has a job:
LockScreenfor notification and island stories; never invented home-screen icons (examples/dynamic-island). - A phone may straddle a window but never covers the amount it explains.
- Several windows: choose the active step; quieter contrast and shadow push the others back (
examples/hero-demo).
A page of demos
- One figure language: one ground, one gutter, one window shadow (
--ss-shadow-hero), one radius set; a different shadow says something or is a mistake. - Fewer, stronger demos. No little labeled containers or text tiles when the demo says it.
- A comparison pair shows a different demo from the one just above it.
- A bad example is marked in the page's own annotation style, never with crosses or scrawls.
Recipes
- Scope the CSS to the demo: nest the stylesheet under its root class (
@keyframesoutside); a prefix is not enough. Hosts styling prose by class exclude stages with:not(:where(.ss-stage *)). compactis a second design for the phone column, not a squeeze (framing.md, Small formats and the page).- Check a fresh load at each width and a resize between: targets, clips, overlays, poster.
- A grid of cards is planned with
bento.md.