CLI
Use the Visor CLI to add components, hooks, and utilities to your project.
Installation
The CLI is distributed as @loworbitstudio/visor-core. No global install required — use npx to run commands directly.
Commands
visor init
Initialize Visor in your project. Creates a visor.json config file with default path mappings.
npx @loworbitstudio/visor initOptions
| Flag | Description |
|---|---|
--template nextjs | Scaffold a complete runnable Borealis-native Next.js app in one command |
--for <play> | Bootstrap a Playbook play (pattern-build, new-web-app, feature-addition) alongside the scaffold |
--play-name <name> | Name for the play instance (defaults to the current directory name) |
--theme <id> | Theme id to record in the play's state metadata |
--from <ref> | Brief source for the play (e.g. a PL-N Linear ticket) |
--json | Emit structured JSON for AI agents — success, files.created, nextSteps, play |
Template scaffolding (one-command flow):
In an empty directory:
npx @loworbitstudio/visor init --template nextjsThis shells out to create-next-app with pinned flags, then layers Visor on top to produce a runnable app:
| File | Source | Purpose |
|---|---|---|
package.json | create-next-app + visor deps | next, react, TypeScript, plus @loworbitstudio/visor-core and @loworbitstudio/visor-theme-engine |
app/page.tsx | create-next-app | Default page |
app/layout.tsx | Visor (overwrites default) | Imports globals.css, injects FOWT prevention inline in <head>, and applies the theme's declared color-scheme at the root (see below) |
app/globals.css | Visor adapter | CSS custom properties generated from .visor.yaml |
.visor.yaml | Visor starter | Editable theme — colors, typography, radius, shadows, color-scheme |
tsconfig.json, next.config.ts, .gitignore | create-next-app | Standard scaffolding |
.lo/borealis.json | Visor | Stamp recording the Visor version that initialized the project |
visor.json | Visor | CLI path configuration |
After it finishes:
cd my-app && npm run devThe dev server starts immediately — no FOWT flash on hard reload, no missing tokens, no extra setup steps.
The generated app/layout.tsx applies the theme's declared color-scheme at the root so the app renders in the right mode from first paint:
dark-only→<html className="dark">with an inlinecolor-scheme: darkand a FOWT script forced todefaultTheme: "dark", so a dark-only brand renders dark even on a light-preference browser.light-only→ the inverse.adaptive(the starter default, or no field) → the default FOWT script that followsprefers-color-scheme.
--template nextjsrefuses ifpackage.jsonalready exists in the target directory. To retrofit an existing app, see the Migration Guide.
Play bootstrap (--for {play-type}):
--for makes Visor the single entry point for starting a Playbook play. It is additive to --template nextjs — you can use either alone or combine them:
# Just the play orchestration structure (no app scaffold)
npx @loworbitstudio/visor init --for pattern-build --play-name organization-management --theme entr
# A fresh NextJS app that also enters the Playbook lifecycle
npx @loworbitstudio/visor init --for new-web-app --play-name my-app --template nextjsKnown play types:
| Play | .lo/ subdirectory | Use for |
|---|---|---|
pattern-build | .lo/pattern-builds/{name}/ | Design + converge a reusable Borealis pattern |
new-web-app | .lo/new-web-apps/{name}/ | A fresh NextJS app entering the Playbook lifecycle |
feature-addition | .lo/feature-additions/{name}/ | An existing project onboarding mid-stream (safe, idempotent) |
The known-plays table is static and Playbook-owned by convention — an unknown --for value errors with the list of known plays. What --for does:
- Play state — writes
.lo/{subdir}/{name}/state.jsonatphase: 0with metadata (play,name,theme,devPort,createdWith). Structural write only — semantic phase content stays owned by the play. - Dev port — allocates a port via
/lo-portswhen alo-portscommand is available, otherwise falls back to a deterministic heuristic port and prints a warning. - Entry checklist — prints the play's next-phase checklist from
~/.claude/skills/lo-play/{play-type}/entry-checklist.md. If the file is missing it prints a fallback pointing you at/lo-play {play-type}.
Re-running --for with the same --play-name is idempotent — it reports the existing state and does nothing else.
✓ Playbook state: .lo/pattern-builds/organization-management/state.json (phase 0)
✓ Dev port allocated: 4210 (via /lo-ports)
✓ Theme recorded: entr
Next steps (from ~/.claude/skills/lo-play/pattern-build/entry-checklist.md):
- [ ] Confirm the shape (admin-ui, forms, ...)
- [ ] Dispatch the design phase: /lo-play pattern-buildDefault config:
{
"paths": {
"components": "components/ui",
"hooks": "hooks",
"lib": "lib"
}
}Edit visor.json to change where files are placed. For example, a Next.js App Router project might use:
{
"paths": {
"components": "src/components/ui",
"hooks": "src/hooks",
"lib": "src/lib"
}
}visor add
Add one or more components, hooks, or utilities to your project.
npx @loworbitstudio/visor add button
npx @loworbitstudio/visor add button input card
npx @loworbitstudio/visor add use-debounceThe CLI resolves transitive registry dependencies automatically. For example, visor add button also adds the utils library since Button depends on it.
npm dependencies (like @radix-ui/react-dialog or class-variance-authority) are installed automatically.
If visor.json doesn't exist, visor add creates one automatically with default paths. You can skip visor init and go straight to adding components.
Options
| Flag | Description |
|---|---|
--overwrite | Overwrite existing files. Without this flag, existing files are skipped. |
--category <name> | Install every item in a category instead of naming items individually. |
--block | Install blocks instead of components. |
--with-suggested | Also install a block's suggested slot-fill components. See below. |
--target <platform> | Target platform: react (default) or flutter. |
--dry-run | Preview what would be added without writing files. |
--json | Output structured JSON (for AI agents). |
Hard vs. suggested block dependencies
A block only installs the components it imports to render itself (its hard
registryDependencies). Slot-driven blocks like admin-shell also declare
suggested slot-fill components — example fillers for their slots that the
block does not import. These are skipped by default so a slot-only compose
doesn't pull an unused component tree (and its npm deps, e.g. Radix).
# Installs only the hard deps (admin-shell + utils) — no Radix
npx @loworbitstudio/visor add admin-shell --block
# Also installs the suggested slot-fill components (breadcrumb, dropdown-menu, sidebar)
npx @loworbitstudio/visor add admin-shell --block --with-suggestedWhen suggested components are skipped, the CLI prints them (and lists them under
suggested in --json output) so you can add them on demand.
visor list
List all available registry items, grouped by type (Components, Hooks, Utilities). Items already installed in your project are marked.
npx @loworbitstudio/visor listvisor diff
Show unified diffs between your local files and the registry versions. Useful for seeing what's changed upstream since you installed a component.
npx @loworbitstudio/visor diff # Diff all installed items
npx @loworbitstudio/visor diff button # Diff a specific componentvisor suggest
Suggest components, blocks, patterns, and hooks for a natural language use case. Scores all manifest entries by keyword overlap and returns the top 10 ranked results with install commands. Designed for AI agent workflows.
npx @loworbitstudio/visor suggest --for "dropdown with search"
npx @loworbitstudio/visor suggest --for "user login form" --json
npx @loworbitstudio/visor suggest --for "dashboard with sidebar" --jsonOptions
| Flag | Description |
|---|---|
--for <useCase> | (Required) Natural language description of what you want to build |
--json | Output structured JSON (for AI agents) |
JSON output shape
{
"success": true,
"query": "dropdown with search",
"results": [
{
"name": "combobox",
"type": "component",
"category": "form",
"score": 2,
"description": "A combobox/autocomplete component with filterable dropdown...",
"match_reason": "Matched: dropdown, search",
"install_command": "npx visor add combobox"
}
],
"summary": {
"total_searched": 123,
"total_matched": 8
}
}type is one of "component", "block", "pattern", or "hook". install_command is null for patterns (no install; they are reference architectures).
visor theme apply
Generate CSS from a .visor.yaml theme file.
npx @loworbitstudio/visor theme apply .visor.yaml
npx @loworbitstudio/visor theme apply .visor.yaml --adapter nextjs
npx @loworbitstudio/visor theme apply .visor.yaml --adapter nextjs --scope-prefix 'body.my-theme'
npx @loworbitstudio/visor theme apply .visor.yaml --adapter fumadocs
npx @loworbitstudio/visor theme apply .visor.yaml --adapter deckOptions
| Flag | Description |
|---|---|
-o, --output <path> | Custom output file path |
--adapter <name> | Target adapter: nextjs, fumadocs, deck, docs, flutter |
--scope-prefix <selector> | (nextjs) Wrap all rules under a CSS selector (e.g. body.mybrand-theme) so multiple themes can coexist via body-class repaint. Dark-mode block scopes to <selector>.dark. Default: :root. |
--json | Output structured JSON (for AI agents) |
See Adapters for details on each adapter's output.
visor theme register
Register a theme in the Visor docs site in one command. Creates the CSS file, adds the @import to globals.css, and adds the theme entry to theme-config.ts — all in alphabetical order. Idempotent: running it twice produces no changes.
npx @loworbitstudio/visor theme register .visor.yaml --group "Client"
npx @loworbitstudio/visor theme register .visor.yaml --group "Visor" --dry-runOptions
| Flag | Description |
|---|---|
--group <name> | (Required) Theme group to register in: Visor, Client, Low Orbit |
--dry-run | Show what would change without writing files |
--json | Output structured JSON (for AI agents) |
visor theme unregister
Remove a theme from the docs site. Deletes the CSS file, removes the @import from globals.css, and removes the entry from theme-config.ts.
npx @loworbitstudio/visor theme unregister entrOptions
| Flag | Description |
|---|---|
--json | Output structured JSON (for AI agents) |
visor theme validate
Validate a .visor.yaml file against the schema and run WCAG contrast checks.
npx @loworbitstudio/visor theme validate .visor.yamlOptions
| Flag | Description |
|---|---|
--json | Output structured JSON (for AI agents) |
--strict-dark | Promote DARK_LIGHT_PARITY warnings and missing dark-neutral to errors (enforces the "always both modes" convention — see Creating Themes) |
--strict-dark in CI:
Add --strict-dark to your CI validate step to enforce the "always both modes" rule as a hard gate:
# Fail CI if any theme is missing its dark neutral
npx @loworbitstudio/visor theme validate .visor.yaml --strict-darkThis flag is opt-in today. Once all themes in your project supply colors-dark.neutral, you can make it the default by adding --strict-dark to every validate call in CI.
visor check theme-mode
Deterministic theme-mode gate. Reads a theme's declared color-scheme (dark-only | light-only | adaptive) and asserts that the app-root background (--surface-page) luminance matches the declared mode. Catches the failure class where a dark-only brand ships a light app root (or a light-only brand ships a dark one) — a rendered-mode regression that structural oracle/freeze gates cannot see.
npx @loworbitstudio/visor check theme-mode ./my-theme.visor.yaml
npx @loworbitstudio/visor check theme-mode ./my-theme.visor.yaml --json
npx @loworbitstudio/visor check theme-mode ./my-theme.visor.yaml --no-faildark-only→ the dark-scope app-root background must be dark (luminance< 0.2).light-only→ the light-scope app-root background must be light (luminance>= 0.2).adaptive(or nocolor-scheme) → skipped — no single mode to assert.
The gate is fully deterministic and dependency-light: it reuses the theme engine's own resolution to compute the host page background the emitted CSS would carry, then reuses getLuminance() — no browser required.
Options
| Flag | Description |
|---|---|
--json | Output structured JSON ({ pass, mode, computed_bg, luminance, ... }) for pipeline wiring |
--format <format> | json or human (default: human) |
--no-fail | Report without exiting 1 on a mismatch (advisory mode) |
On failure, computed_bg reports the offending computed background color and the command exits 1.
visor theme export
Export a minimal .visor.yaml from an existing theme.
npx @loworbitstudio/visor theme export .visor.yamlvisor migrate token-substitution
Mechanically apply the §3.1 V7-primitive → Visor-semantic substitution table across a target directory or file. This is the codemod that makes theme portability automatic — the single highest-leverage step in porting V7-primitive components to Visor-semantic tokens.
Dry-run by default. Use --apply to commit changes to disk. Running twice is a no-op (idempotent).
# Preview proposed changes (dry-run)
npx @loworbitstudio/visor migrate token-substitution components/local/
# Apply changes
npx @loworbitstudio/visor migrate token-substitution components/local/ --apply
# Target a single file
npx @loworbitstudio/visor migrate token-substitution components/local/KpiCard.module.css --apply
# Apply for a specific theme (default: entr)
npx @loworbitstudio/visor migrate token-substitution --theme-id entr --apply
# Machine-readable output for AI agents / CI
npx @loworbitstudio/visor migrate token-substitution components/ --jsonOptions
| Flag | Description |
|---|---|
[path] | Path to a file or directory to migrate (default: current directory) |
--theme-id <id> | Theme whose substitution map to apply (default: entr) |
--dry-run | Preview proposed changes without writing files (this is the default when --apply is omitted) |
--apply | Write changes to disk |
--json | Output structured JSON (for AI agents) |
The §3.1 substitution table (V7 ENTR → Visor semantic)
This table encodes the single highest-leverage round-2 refactor from the Borealis prototype-to-Visor exercise. Applying it at component-build time makes theme swaps produce visibly distinct, structurally correct repaints with zero shim CSS.
| V7 primitive | Visor semantic | Notes |
|---|---|---|
--panel | --surface-card | Primary surface tier |
--panel-2 | --surface-interactive-default | Interactive resting state; use --surface-subtle for decorative areas |
--panel-3 | --surface-interactive-active | Pressed/active state; use --surface-interactive-hover for hover |
--text | --text-primary | Primary text |
--text-2 | --text-secondary | Secondary/muted text |
--text-3 | --text-tertiary | Tertiary/dimmed text |
--text-4 | --text-tertiary | No dedicated Visor equivalent; maps to tertiary |
--mint | --accent-primary | Brand accent (fills, icons); for text use --text-success |
--mint-soft | --surface-accent-subtle | Accent tint; use --surface-selected for selected-row highlights |
--warn | --text-warning | Warning text and icons |
--warn-soft | --surface-warning-subtle | Warning background tint |
Brand-local tokens excluded (intentional): --screen (deepest surface tier), --font-marquee, and the discrete type scale (--text-11 through --text-72) have no Visor semantic equivalent and must remain brand-local.
Adding a theme's substitution map to its .visor.yaml
Themes can declare their own substitution table in the migrate section of their .visor.yaml. The CLI reads from this file first (if found), then falls back to the built-in registry:
name: my-theme
version: 1
colors:
primary: "#10B981"
# ... other theme fields ...
migrate:
token-substitution:
--brand-surface: --surface-card
--brand-text: --text-primary
--brand-accent: --accent-primaryJSON output shape
{
"success": true,
"dryRun": true,
"themeId": "entr",
"targetPath": "/path/to/components",
"filesScanned": 10,
"filesChanged": 3,
"totalSubstitutions": 12,
"files": [
{
"file": "/path/to/components/KpiCard.module.css",
"substitutions": [
{
"line": 2,
"column": 14,
"from": "--panel",
"to": "--surface-card",
"originalLine": " background: var(--panel);",
"replacedLine": " background: var(--surface-card);"
}
],
"newContent": "..."
}
]
}visor sandbox
Scaffold a Next.js app for in-vivo primitive iteration — populated with real Visor primitives (via visor add) plus visible placeholder stubs for primitives not yet shipped. Used by the Low Orbit pattern-build pipeline to iterate on new primitives in their target composition before they're approved and implementation work begins.
The CLI itself doesn't bundle Playwright — the sandbox scaffold declares @playwright/test in its own devDependencies, so sandbox approve runs inside the sandbox and the published @loworbitstudio/visor package stays light.
visor sandbox init <name>
npx @loworbitstudio/visor sandbox init org-mgmt \
--handoff ~/path/to/design-handoff.md \
--theme entrReads a Low Orbit design-handoff.md manifest, scaffolds a Next.js app at .lo/sandbox/<name>/, runs visor add for every shipped primitive declared in the manifest, generates a visible dashed-border stub at components/stubs/<name>.tsx for each gap primitive (each carrying a GAP: VI-<NNN> overlay), applies the named theme, and writes sandbox.json recording the port, primitives, and screens.
| Flag | Description |
|---|---|
--handoff <path> (required) | Path to a Low Orbit design-handoff.md manifest |
--theme <slug-or-path> (required) | Theme slug or path to a .visor.yaml. Resolution order: --theme-file if set → <slug> as a direct path → ${VISOR_THEMES_PRIVATE_PATH}/themes/<slug>/theme.visor.yaml if the env var is set → themes/<slug>.visor.yaml → custom-themes/<slug>.visor.yaml |
--theme-file <path> | Explicit path to a theme.visor.yaml — bypasses name resolution and the VISOR_THEMES_PRIVATE_PATH env var |
--from-html-prototype <path> | Import a Phase 1.5 HTML prototype directory: copies its files into public/prototype/ and pairs each numerically-prefixed screen-N-*.html with the matching manifest screen, rendering it via an iframe in the screen route |
--strip-chrome [selectors] | Strip Phase 1.5 documentary chrome from imported prototype HTML before it lands in public/prototype/. Bare flag uses the default selector list (.state-callout, .state-section__header, .proto-nav, [data-documentary-chrome], [style*="mint"]); a comma-separated list REPLACES the defaults |
--strip-chrome-additional <selectors> | Comma-separated selectors to MERGE with the chosen --strip-chrome base (defaults or replacement). Useful when a pattern has one or two extra chrome variants on top of the standard set |
--overwrite | Replace an existing sandbox at this name |
--skip-install | Skip npm install (test fixture mode) |
--json | Emit structured JSON for AI agents |
Resolving brand themes from a private repo. Set VISOR_THEMES_PRIVATE_PATH to the root of your private themes directory (e.g. ~/Code/low-orbit/visor-themes-private) and --theme <slug> will resolve to ${VISOR_THEMES_PRIVATE_PATH}/themes/<slug>/theme.visor.yaml automatically. For one-off overrides, pass --theme-file <path> to point at any .visor.yaml directly — useful when the theme lives outside the conventional layout.
When --from-html-prototype is set, the sandbox boots with the real Phase 1.5 composition as the baseline — useful for pattern builds whose Phase 1.5 cleared before this CLI shipped. The mapping is order-based: the Nth manifest screen pairs with the Nth screen-*.html file (sorted by numeric prefix). The resolved map is recorded in sandbox.json under fromHtmlPrototype.screenMap.
State-coverage screens are auto-discovered. Any screen-N-*.html files beyond the manifest's named-screen count are appended to the sandbox manifest as state-coverage screens with predictable slugs derived from the filename suffix (e.g., screen-5-menus.html → state-coverage-menus, screen-7-edge-states.html → state-coverage-edge-states). Each gets its own iframe-loading route at /screens/<slug> and a recorded entry in sandbox.json under fromHtmlPrototype.stateCoverageScreens. This powers the Phase 4 state-coverage diff gate (states_prototyped: { menu, feedback, edge }).
Documentary chrome stripping (--strip-chrome) addresses a Phase 1.5 vs Phase 7 mismatch. Phase 1.5 HTML prototypes embed documentary chrome — state callouts, section headers, proto-nav, mint-styled annotation chips — to label states for the design reviewer. The reference build's scaffold never renders those labels, so Phase 7 scaffold-drift checks fail on every screen with documentary chrome unless stripped first. Pass bare --strip-chrome to remove the standard chrome set, --strip-chrome ".a,.b" to swap in a custom list, or --strip-chrome-additional ".x,.y" to merge extras with the base. Supported selector shapes: class selectors (.foo), attribute-presence selectors ([data-foo]), and attribute-substring selectors ([style*="mint"]). The stripper runs over .html files only — sibling CSS/JS assets in the prototype tree are copied byte-for-byte. The resolved selector list is recorded in sandbox.json under fromHtmlPrototype.stripChromeSelectors.
visor sandbox dev
npx @loworbitstudio/visor sandbox dev --name org-mgmtBoots next dev for the sandbox on its allocated port. Auto-allocated ports start at 4060 and never use port 3000 (reserved per the Low Orbit convention). Prints the base URL and one URL per route (/, /primitives/<name>, /screens/<name>) before spawning.
visor sandbox approve
# 1. Capture into pending (auto-diff vs approved if a baseline exists)
npx @loworbitstudio/visor sandbox approve --name org-mgmt
# 2. Review captures/pending/ and captures/diffs/
# 3. Promote pending → approved once the captures look right
npx @loworbitstudio/visor sandbox approve --name org-mgmt --approveSandbox captures use a three-state review flow:
- Default capture — Shells out to a sandbox-local Playwright install and captures full-page screenshots of every route into
captures/pending/<route-slug>.png. Ifcaptures/approved/already contains a baseline, every pending capture is pixel-diffed against it and any differences land incaptures/diffs/. The approved baseline is never touched. - Operator review — Eyeball
captures/pending/andcaptures/diffs/. Re-run capture to iterate until the pending set is correct. - Promotion —
--approvecopiescaptures/pending/→captures/approved/and clears the pending + diffs directories. Captures live in the approved baseline only after a deliberate operator action.
| Flag | Description |
|---|---|
--name <name> (required) | Sandbox name (from a prior sandbox init) |
--approve | Promote captures/pending/ → captures/approved/. Fails with a hint if pending is empty — run the default capture first. |
--diff | Deprecated. No-op; default capture already pixel-diffs against the approved baseline. Kept for backwards compatibility. |
--json | Emit structured JSON for AI agents |
visor spawn
Fork a Playbook blessed reference build — one command replaces the manual cp -R + npm install + visor theme apply dance. Copies the build's tree into a new project, re-skins it with a theme (atomically), and optionally installs dependencies.
A blessed build is any directory that ships a blessed-manifest.json at its root. See Blessed Builds for the manifest contract.
npx @loworbitstudio/visor spawn \
--from blessed:admin-ui:organization-management \
--theme entr \
--output ~/tmp/org-admin \
--install✓ Discovered blessed build: admin-ui/organization-management (requires visor >=1.14.0)
✓ Forked to ~/tmp/org-admin (excluded node_modules, .next, .git)
✓ Theme applied: entr → ~/tmp/org-admin
✓ Installed dependencies
Next: cd ~/tmp/org-admin && npm run devThe source identifier is a namespaced string, blessed:{shape}:{pattern} (shape-agnostic) — visor spawn resolves it by matching blessed-manifest.json fields, not by directory path.
Options
| Flag | Description |
|---|---|
--from <identifier> | Blessed build to spawn: blessed:{shape}:{pattern} |
--theme <id> | Theme id (or path to a .visor.yaml) to re-skin the fork. Resolution mirrors sandbox init: --theme-file → direct path → ${VISOR_THEMES_PRIVATE_PATH}/themes/<id>/theme.visor.yaml → themes/<id>.visor.yaml → custom-themes/<id>.visor.yaml |
--theme-file <path> | Explicit path to a theme.visor.yaml — bypasses --theme name resolution |
--output <path> | Destination directory for the forked project (must be empty or non-existent) |
--blessed-dir <path> | Override the blessed-build root. Precedence: --blessed-dir → VISOR_BLESSED_DIR → ~/Code/low-orbit/low-orbit-playbook/design-prototypes |
--install | Run npm install in the fork (default: skip) |
--validate | Validate the applied theme after forking; validation errors roll the spawn back (default: skip) |
--list-blessed | List all discoverable blessed builds and exit |
--json | Output structured JSON (for AI agents) |
The fork excludes node_modules, .next, .git, and other transient dirs, and creates an independent project with no upstream tracking. Theme application is atomic: if it (or --validate) fails, the output directory is deleted, so you get a clean fork or nothing.
# Discover what's available
npx @loworbitstudio/visor spawn --list-blessed
# Point at a custom blessed-build root
VISOR_BLESSED_DIR=~/my/blessed npx @loworbitstudio/visor spawn --list-blessedvisor render
Render one real component to a PNG using the real emitted tokens, the real per-theme CSS, and the real component .tsx — fully serverless (no next dev). Fast enough to run per-component per-PR as a render-fidelity gate.
npx @loworbitstudio/visor render doc-nav --theme space --mode dark
npx @loworbitstudio/visor render stat-card --theme neutral --mode light --out shot.pngThe harness composes CSS in the correct cascade order — packages/tokens/dist/tokens.css → the theme's real per-mode CSS from packages/docs/app/<slug>-theme.css (which self-declares its @layer order and scopes each mode by ancestor) — then esbuild-bundles the real component with a representative fixture and its default variant classes, mounts it under the theme scope, and screenshots it with Playwright.
Because the theme class lives on #theme-scope (not <html>), a computed-style probe reads --surface-card from both the themed element and the raw <html> root and confirms the themed surface resolved to its mapped value, not the base primitive — the exact drift class (medium-gray pill, ballooned group radius) that otherwise has to be caught by hand:
✓ Rendered doc-nav · space · dark
→ .visor/renders/doc-nav__space__dark.png (44.9 KB)
✓ Themed --surface-card resolved to #0e0e18 (base primitive: #111827) — theme mapping applied.Options
| Flag | Description |
|---|---|
--theme <slug> | Required. Theme slug (e.g. space, neutral) — resolves packages/docs/app/<slug>-theme.css |
--mode <mode> | Color mode: light (default) or dark |
--state <state> | Interactive state to capture: default, hover, focus, or active |
--fixture <name> | Named fixture for the component (default: default) |
--out <path> | Output PNG path (default: .visor/renders/<component>__<theme>__<mode>.png) |
--width <px> / --height <px> | Viewport size (defaults: 720 × 640) |
--json | Output structured JSON (including the computed-style probe) for AI agents |
Optional dependencies. visor render needs playwright and esbuild. They are not bundled into the CLI — so consumers who never render pay no install-size cost — and are loaded lazily only when the command runs. If either is absent, the command prints a clear install prompt:
npm install -D playwright esbuild && npx playwright install chromiumTypical Workflow
# 1. Scaffold a complete runnable Borealis-native Next.js app
npx @loworbitstudio/visor init --template nextjs
# 2. Start the dev server
npm run dev
# 3. Add components
npx @loworbitstudio/visor add button card input
# 4. Customize freely — you own the files
# 5. Later, check for upstream updates
npx @loworbitstudio/visor diff
# 6. Pull updates for specific components
npx @loworbitstudio/visor add button --overwriteKnown Limitations
Turbopack and symlinked packages
Turbopack (used in next dev --turbopack) cannot resolve CSS @import paths
through symlinked node_modules. This affects local file: or link: installs
of @loworbitstudio/visor-core.
Workarounds:
-
Use
--template nextjs(recommended) — the scaffoldedapp/globals.cssis generated inline by the Next.js adapter. No CSS@importof@loworbitstudio/visor-coreis required, so Turbopack's symlink resolver never enters the picture.npx @loworbitstudio/visor init --template nextjs -
Use a relative path import instead of the package name:
/* Instead of: @import '@loworbitstudio/visor-core'; */ @import '../../path-to/visor-core/dist/tokens.css'; -
Use webpack — run
next devwithout--turbopackto use webpack's resolver, which handles symlinked CSS correctly.