SFT — Screens, Flows, Transitions
A lightweight vocabulary for making implicit UI structure explicit. Event-driven, layered state machines in YAML.
SFT sits between Figma (visual design) and PRDs (requirements) — a behavioral contract layer that captures what the user sees and does at the screen/region level. Write it once, hand it to designers, engineers, and PMs.
Install
npm i -g sft-cli
Binaries also available from GitHub Releases.
Quick Start
# Import an existing spec
sft import examples/linear.sft.yaml
# Explore
sft show # full spec tree (human + LLM readable)
sft show --json # structured JSON
sft query screens # list all screens
sft query events # list all events
sft query states Home # transitions for a screen
# Validate
sft validate # check for orphans, dead events, cycles
# Round-trip
sft export spec.yaml # serialize current DB state back to YAML
sft diff examples/linear.sft.yaml # compare current vs a file
CLI Reference
All commands support --json for structured output. The spec lives in .sft/db (auto-created).
Reading
sft show # full spec tree
sft query <type> # screens | regions | events | flows | tags | attachments
sft query states <name> # transitions for a screen/region
sft query steps <flow> # parsed flow steps
sft query "SELECT ..." # raw SQL against the spec DB
sft impact <screen|region> <name> # what depends on this entity
Mutating
sft add app <name> <desc>
sft add screen <name> <desc>
sft add region <name> <desc> --in <parent>
sft add event <name> --in <region>
sft add transition --on <event> --in <owner> [--from <s>] [--to <s>] [--action <a>]
sft add tag <tag> --on <entity>
sft add flow <name> <sequence> [--description <d>] [--on <event>]
sft set <screen|region> <name> --description <new> [--in <parent>]
sft rename <screen|region|flow> <old> <new> [--in <parent>]
sft rm <screen|region|event|transition|tag|flow> <name> [--in/--on <parent>]
sft mv region <name> --to <parent> [--in <current-parent>]
sft reorder <parent> <child1> <child2> ...
Components
Bind UI component types to screens or regions for code generation:
sft component Home # show bound component (JSON)
sft component Home Dashboard --props '{"layout":"grid"}'
sft component Sidebar NavPanel --props-file sidebar.json --on handleClick --visible auth
sft component Home --rm # unbind
Components round-trip through export/import — they're preserved in YAML.
Import / Export / Diff
sft import spec.yaml # load YAML into fresh DB
sft export [file.yaml] # serialize to YAML (stdout if no file)
sft diff spec.yaml # compare current spec vs YAML file
Browser View
sft view only works when the frontend bundle is embedded in the current build.
If the binary prints that the frontend is not bundled, use a release build or rebuild the embedded web assets first.
Attachments
sft attach Home mockup.png --as wireframe.png
sft attach Home mockup.png --content-id figma:node123 # link to external source
sft set attachment Home mockup.png --content-id figma:updated # update link
sft list # all attachments (shows content-id + hash)
sft list Home # attachments on Home
sft cat Home wireframe.png # read attachment content
sft detach Home wireframe.png
sft query attachments # all attachments with content tracking metadata
Scoped Regions
Region names can repeat across different parents. Use --in to disambiguate:
sft add region Header "Top bar" --in Settings
sft add region Header "Top bar" --in Profile # same name, different parent
sft rm region Header --in Settings
sft rename region Header TopBar --in Profile
Rendering (json-render)
sft render generates a json-render-compatible spec — a flat element tree that a UI runtime can consume directly.
sft render # full json-render spec to stdout
sft render | jq '.elements.Home' # inspect one element
The pipeline:
- Skeleton — every screen becomes a
Card, every region a Stack, children wired by hierarchy
- Hydrate — components bound via
sft component override the element type, props, on handlers, and visible conditions
# Without components — generic skeleton
sft render | jq '.elements.MyIssues'
# {"type": "Card", "props": {"title": "MyIssues"}, "children": ["IssueList", "FilterBar", ...]}
# Bind a component
sft component MyIssues DataTable --props '{"cols":["title","status"],"selectable":true}'