CLI reference
Every flag for `domotion capture` and `domotion animate`.
The domotion binary is installed when you
npm install domotion-svg. It exposes two commands:
domotion capture <input> [options]— single-frame SVG.domotion animate <config.json>— multi-frame animated SVG.
capture
domotion capture <input> [options]
<input> is one of:
- A URL —
https://example.comorhttp://localhost:3000/path. - A local HTML file path.
-to read HTML from stdin.
| Flag | Default | Effect |
|---|---|---|
-o, --output <path> | stdout, or <input>.svg for files | Where to write the SVG. Use -o - to stream to stdout explicitly. |
--width <n> | 800 | Viewport width in CSS pixels. |
--height <n> | 600 | Viewport height in CSS pixels. |
--selector <css> | "body" | Element to capture; the SVG will be the size of this element's bounding box (clipped by --clip if present). |
--clip x,y,w,h | full viewport | Capture only this rectangle. Useful for cropping just one card or section out of a larger page. |
--scroll-to x,y | 0,0 | Scroll the page to this offset before capturing — for capturing below-the-fold content. |
--scroll <pattern> | — | Generate an animated scroll demo from the page. Captures at multiple scroll positions and composes one animated SVG (DM-604). Examples: "down:bottom/8s", "720px,2s until bottom". |
--scroll-speed <n> | 1500 | Default scroll speed in px/s for tokens in --scroll that don't specify /<duration>. |
--scroll-selector <css> | window | Scroll a specific element instead of the window, for the --scroll animated-demo flow. |
--no-prescroll | off | Skip the pre-scroll-to-bottom-then-top step used to wake lazy-loaded content before the --scroll demo records. |
--wait <ms> | 200 | Sleep this long after the page settles. Bump up if you have lazy-loaded images or CSS transitions in flight. |
--wait-for <css> | — | Wait for the matching element to become visible before capturing. Most reliable for SPA-driven content. |
--no-fonts-ready | off | Skip the document.fonts.ready wait. By default Domotion waits for web fonts. |
--optimize | off | Run output through SVGO. 30–50% smaller; reduces decimal precision so diffs get noisier. |
--warnings | off | Log capture warnings to stderr after the run (CSS features that didn't round-trip). |
--mobile | off | Emulate a mobile device (iOS user-agent, isMobile=true). Sites that branch on UA or use @media (hover: none) render their mobile path. |
--color-scheme <s> | — | Sets prefers-color-scheme: light, dark, or no-preference. Pages that use @media (prefers-color-scheme: dark) render their dark variant. |
capture examples
# Default: capture the whole body of example.com at 800×600.
domotion capture https://example.com -o example.svg
# Capture only the .pricing-table at retina-friendly 1280×720,
# wait for it to load, optimize.
domotion capture https://yoursite.com/pricing \
--width 1280 --height 720 \
--selector ".pricing-table" \
--wait-for ".pricing-table .price" \
--optimize \
-o pricing.svg
# Capture a local HTML file, only the bottom-right 400×200 region.
domotion capture ./demo.html --clip 200,200,400,200 -o slice.svg
# Capture HTML produced by a build step, no temp file.
node build-demo-html.js | domotion capture - --width 600 --height 200 -o out.svg
# Mobile capture: iOS user-agent + isMobile.
domotion capture https://example.com \
--width 390 --height 844 --mobile -o phone.svg
# Capture a dark-mode-aware page in dark mode.
domotion capture https://example.com --color-scheme dark -o dark.svg
animate
domotion animate <config.json> [-o path] [--optimize]
<config.json> describes one or more frames; each frame is
captured (optionally after running an interaction) and the result is stitched
into a single animated SVG with CSS keyframe transitions.
Config schema
{
"width": 800,
"height": 400,
"output": "demo.svg", // optional, overridden by -o
"optimize": true, // optional, --optimize on the CLI also works
"chrome": { "type": "browser", "url": "https://yoursite.com" }, // optional
"frames": [
{
"input": "./frames/start.html", // or a URL
"duration": 1500, // ms held on screen
"transition": { "type": "crossfade", "duration": 300 },
"selector": "body", // optional capture root
"wait": 200, // optional ms after settle
"waitFor": ".ready", // optional CSS selector
"scroll": [0, 0], // optional [x, y]
"actions": [...], // see "Actions" below
"overlays": [...] // see "Overlays" below
}
]
}
Paths in "input" resolve relative to the config file's
directory. Each frame opens its input in the same Playwright context, runs
its actions (if any), waits the requested time, and is captured.
Transitions
| Type | What it does |
|---|---|
"crossfade" | Old frame fades out as new frame fades in. Best for shared layouts where individual elements are crossing over. |
"cut" | Instant — the next frame replaces the current one with no fade or slide. duration is ignored. Best for "this just changed" beats: a progress bar resizing, a new line appearing in a terminal, a panel toggling. |
"push-left" | New frame slides in from the right, old frame slides out to the left. Best when the two frames are unrelated screens. |
"scroll" | Vertical scroll between frames; treats them as a single tall canvas. Best for "scroll through the page" demos. |
Actions
Actions run in order before the frame is captured. Each is a simple object:
{ "type": "click", "selector": ".cta" }
{ "type": "fill", "selector": "input[name=q]", "value": "hello" }
{ "type": "press", "key": "Enter" }
{ "type": "hover", "selector": ".tooltip-trigger" }
{ "type": "scroll", "y": 200 }
{ "type": "wait", "ms": 300 }
Use wait to give animations / async UI a chance to settle
between actions.
Animations (intra-frame)
Animations declared on a frame run during its hold time. Use them for
property changes that happen within a single frame: progress bar
filling, content scrolling, an element fading in. Each animation resolves
its selector against the captured DOM and tags matching elements
with a CSS class the renderer targets via keyframes.
{ "selector": ".bar", "property": "transform",
"from": "scaleX(0)", "to": "scaleX(1)",
"duration": 2000, "easing": "ease-out", "delay": 150 }
Supported property values: transform,
opacity, width, height,
translateX, translateY. Use full transform strings
(scaleX(0.5), rotate(15deg)) for the
transform property. The translateX /
translateY shorthand expects raw values
("240px", "-50%").
For "fill from left" effects (progress bars), use
transform: scaleX(0)→scaleX(1) with
transform-origin: left set on the source element. CSS
width animations on SVG-rendered rects don't compose
cleanly because of how SVG percentage units work — scaleX
on a wrapper group is the reliable path.
Overlays
Overlays draw on top of the captured frame so you can simulate typing or taps without modifying the page. Two kinds:
{ "kind": "typing", "text": "hello", "x": 20, "y": 40, "speed": 80 }
{ "kind": "tap", "x": 120, "y": 90, "delay": 600 }
{ "kind": "svg", "src": "./preview.svg", "x": 180, "y": 0,
"width": 280, "height": 580,
"enter": { "from": "bottom", "duration": 600, "easing": "ease-out" } }
The svg overlay inlines a separately-captured SVG file as a
picture-in-picture layer. src is resolved relative to the
config file's directory; ids in the embedded SVG are namespaced
automatically. Use enter / exit for slide-in
sugar — from is one of "top", "bottom",
"left", "right".
See Overlay types for the full property list.
Exit codes
| Code | Meaning |
|---|---|
0 | Capture wrote successfully. |
1 | Runtime error (browser launch failed, page navigation failed, etc). |
2 | Argument / config error. |
When to drop down to the API
- You want a custom interaction loop the JSON action vocabulary can't express (e.g. drag-and-drop, multiple-step flows where the next action depends on a captured value).
- You want to compose frames programmatically — e.g. one frame per row of a database query.
- You're integrating capture into an existing test harness (Playwright, Vitest) and want to share the browser context.
Otherwise, the CLI covers the common cases. See API overview when you outgrow it.