# Roughflow spec for coding agents
Roughflow is a mobile-first, low-fidelity wireframing app. A person roughs out
screens with a small set of shapes, tags each shape's semantic role, wires up
navigation between screens, and exports this document: a structured spec
describing frames, elements, and flow. It is handed to you (Claude Code,
Codex, or any other coding agent) as a build spec. Your job is to read it and
scaffold the real UI it describes.
This file is both a human-readable explainer and the schema reference the
exported YAML's `# schema:` header links to.
## What you're looking at
Every export starts with a two-line comment header:
# Roughflow spec v1
# schema: https://roughflow.com/llms.txt
If the export is scoped to a single frame, a third comment line follows:
# scope: frame (, )
After the header, the document is plain YAML (hand-emitted, not run through a
generic YAML library: expect fixed field order and flow-style `{...}` maps
for small objects like `size`, `frame`, and `repeat`).
## Top-level keys
- `rough`: string. The name of the Roughflow document (a "Rough") this was
exported from.
- `exported`: string. ISO-8601 timestamp of the export.
- `scope`: `full` or `frame`. `full` exports every frame in the Rough;
`frame` exports exactly one frame plus only the flow edges whose source
element lives in it (see the `# scope:` comment line above for which one).
- `frames`: list of frame objects (see below).
- `flow`: list of navigation edges, or `[]` if there are none.
## Frame fields
Each entry under `frames:` is:
- `id`: string, stable identifier.
- `name`: string, user-given frame name (e.g. "Home", "Settings").
- `device`: one of `phone` | `tablet` | `desktop` | `custom`. This is the
frame's preset, not a literal device model, so treat it as a rough size
class, not a pixel-exact target.
- `size`: `{w, h}` in logical pixels. Preset sizes: phone 384x848, tablet
768x1024, desktop 1440x896; `custom` frames carry their own explicit size.
- `elements`: list of element objects (see below), or `[]` if the frame is
empty. Order matters (see "Z-order" below).
## Element fields
Each entry under a frame's `elements:` (or a group/repeater's `children:`) is:
- `id`: string, stable identifier.
- `type`: one of `rect` | `circle` | `text` | `line` | `image` | `group`.
This is the *drawn primitive* (what shape it is), not its semantic role.
`group` is a plain wrapper around `children` with no paint of its own,
used both for a hand-grouped cluster of primitives and, combined with
`repeat` below, as a list/grid repeater's template container.
- `role`: optional. One of the closed set:
`button`, `input`, `heading`, `text`, `image`, `icon`, `list`, `grid`,
`group`, `nav-bar`, `tab-bar`, `frame`, `custom`.
This is the *semantic meaning* a human assigned in the layers view,
independent of `type` (e.g. a `rect` tagged `role: icon` is a square
standing in for an icon; the annotation, not the pixels, is the intent).
Absent `role` means untagged: geometry only, no assigned meaning yet.
- `custom_role`: present only when `role: custom`. Freeform text the user
typed for a case the closed enum doesn't cover.
- `confidence`: present only when `role: custom`, always `low`. Signals to
you that this role is a human's free-text escape hatch, not a vetted
enum value, so treat it as a hint, not a hard contract.
- `name`: optional string. A user-given label for the element, when set.
- `text`: optional string, only ever present on `type: text` elements, and
only when the user actually typed content. A `type: text` element with NO
`text` field is a greeked/placeholder text block (a placeholder bar was
drawn, no real copy yet): treat it as "role + geometry only," not a blank
string to render.
- `frame`: `{x, y, w, h}` in logical pixels, quantized to a 16px grid.
**Coordinates are relative, not absolute** (see "Coordinate system" below).
- `children`: optional list, recursively shaped like this same element list.
Present on `group`-type elements that contain nested elements (a hand-made
group, or a repeater's template container; see `repeat` below).
- `repeat`: optional. `{cols, rows, gap, gap_cross}`, all in logical pixels
except `cols`/`rows` which are counts. Present when this element is a
list/grid repeater: its (single) child under `children` is the template
item, repeated `cols` × `rows` times with `gap` (primary axis spacing) and
`gap_cross` (cross-axis spacing). **Always this 4-field shape, even when
one axis is 1**: a vertical list is `{cols: 1, rows: N, ...}`, a
horizontal list is `{cols: N, rows: 1, ...}`, there is no separate
"list" shape. Roughflow's model has no independent list primitive: list
and grid are the same repeat container, just with a different axis count.
## Coordinate system
- Grid: everything is placed on a 16px logical-pixel grid (both frame element
positions/sizes and repeat gaps are quantized to multiples of 16).
- A **top-level** element's `frame.{x,y}` is relative to its containing
frame's own top-left origin (i.e. `(0,0)` is the frame's top-left corner,
not the canvas/scene origin).
- A **nested** element's `frame.{x,y}` (anything under a `children:` list) is
relative to its immediate parent group/repeater's top-left corner, not the
outer frame. Nest arithmetic accordingly when reconstructing absolute
layout: walk the tree accumulating each level's `{x,y}` offset.
## Z-order
Within a frame's `elements` list (and within any `children` list), **order is
z-order, bottom-first**: the first element in the list paints first / sits
at the back; later entries paint on top. When you scaffold overlapping
elements (e.g. a button on top of a background rect), preserve this order in
your generated component tree (mount order or explicit stacking).
## Flow entries
Each entry under `flow:` is one navigation edge:
- from: {element: , frame: }
to:
transition: slide
This is **element-anchored** navigation, not frame-to-frame: `from.element`
is the specific element a user tapped/interacted with to trigger navigation
(e.g. a button, a nav-bar item, a list-item template), `from.frame` is the
frame that element currently lives in, and `to` is the destination frame's
`id`. When scaffolding, wire this as an onPress/onClick navigation call on
that specific element/component, not a generic screen-level link.
- `transition`: optional. One of `slide` | `fade` | `pop`, defaults to
`slide` when absent. The suggested screen-transition animation for this
navigation, set by the user in the flow layer. Map it to whatever your
target platform's equivalent is (e.g. a push/slide transition, a
crossfade, or a modal-style pop), a hint for feel, not a hard contract.
## Scalars and quoting
String values are printed bare when safe, and double-quoted (with `\` and
`"` escaped) when they contain YAML flow-indicator characters
(`: # { } [ ] , & * ! | > ' " % @` \`), would otherwise parse as a number,
bool, or null, or have leading/trailing whitespace. Standard YAML string
parsing handles both cases correctly, so no custom parser is required.
Numbers print as bare integers when whole (`128`, never `128.0`); Roughflow's
own geometry is always grid-quantized, so you should only see a decimal
if the file was hand-edited outside the app.
## How to turn this into a real app
1. **One frame ≈ one screen.** Scaffold one screen/route/component per frame,
named after `frame.name`, sized to roughly `frame.size` as a starting
layout (phone/tablet/desktop as a breakpoint hint, not a hard pixel lock:
this is a rough spec, not a mockup).
2. **`role` drives the component you generate**, not `type`. A `rect` tagged
`role: button` becomes a real button component; a `rect` tagged
`role: icon` becomes an icon slot (use `name`/`custom_role` as a hint for
*which* icon, if given; there is no icon asset reference, only intent).
Untagged elements (no `role`) are literally just shapes: lay them out but
don't over-infer meaning.
3. **`repeat` → a list/grid component.** Build the single child under
`children` as one reusable template/item component, then render it in a
loop/`ListView`/CSS grid driven by `cols`/`rows`/`gap`/`gap_cross`; don't
hand-copy the template N times.
4. **Plain `group` (no `repeat`) → a layout container**, e.g. a `
`/
`Column`/`Row`/`Stack` wrapping its `children` at their relative offsets,
with no repetition semantics, just grouping.
5. **`flow` → navigation wiring.** For each edge, attach a navigation action
(route push, `Navigator.push`, ``, etc.) from `from.element`'s
generated component to `to`'s generated screen.
6. **`role: custom` / `confidence: low`** elements are the roughest signal in
the document: use `custom_role`'s text as a naming/intent hint, but don't
assume it maps cleanly onto a known component pattern the way the closed
enum roles do.
7. Geometry (`frame: {x,y,w,h}`) is a rough starting layout, not a pixel
contract, since this is deliberately a low-fidelity tool (see the
roughflow.com landing page for the thesis). Prefer semantic, responsive
layout over literally reproducing every coordinate.
## Source of truth
This document is generated from `lib/export/yaml_exporter.dart` in the
Roughflow app. If anything here ever seems to disagree with an actual export
you're holding, trust the file in front of you: this page describes the
shape, not a specific instance.