Velloo documentation
Markdown for LLMs ↗Core concepts
The Board, Frame, and Screen mental model behind every Velloo design folder, and why it's not Pages and Variants.
Velloo has one mental model, and it’s worth internalizing before you compose anything: Board → Frame → Screen. It is deliberately not Pages → Variants.
The three nouns
- Screen: one UI tree. A screen owns its structure and nothing else: no position, no size, no board. There is exactly one tree per screen, stored as
screens/<id>.json. - Frame: a placement of a screen onto a board, at a position and size. The viewport is a property of the frame, not the screen.
- Board: one infinite canvas holding frames. A design folder has many boards, typically one per flow: marketing, app, settings, onboarding. Each persists as
boards/<id>.json.
A board file is small enough to read whole. This one is trimmed from the Elsewhere sample:
{
"id": "main",
"name": "Elsewhere · The journey",
"frames": [
{
"id": "elsewhere-discover-desktop",
"screen": "elsewhere-discover",
"x": 0, "y": 0,
"w": 1440, "h": 1361,
"label": "01 · Discover your elsewhere / Desktop",
"scheme": "light"
},
{
"id": "elsewhere-discover-mobile",
"screen": "elsewhere-discover",
"x": 3100, "y": 0,
"w": 390, "h": 2390,
"label": "Shared screen / Mobile"
},
{
"id": "elsewhere-discover-dark",
"screen": "elsewhere-discover",
"x": 0, "y": 3300,
"w": 1440, "h": 1361,
"label": "Discover / Dark mode",
"scheme": "dark"
}
],
"groups": []
}
Note what a frame holds: a screen reference, coordinates, a size, and optionally a label, a group, and a color scheme. No tree. The tree lives in the screen, once.
The sync model
Two frames pointing at the same screen always render the same underlying tree at different sizes. That’s the whole sync model: there is no per-frame override of the tree, so there’s nothing that can drift. In the board JSON above, all three frames point at elsewhere-discover; edit the hero copy in any one and all of them update, because “all of them” is an illusion: there’s one tree rendering three times.
The canvas makes the relationship visible: when several frames on a board share a screen, each frame header shows a link badge with the count, and hovering a node in one frame highlights the same node in the others. A frame’s menu can also place another frame of the same screen at a preset size.
Frames are freely resizable. Snapping to a viewport preset (mobile / tablet / desktop) is a UI affordance, not a data constraint; the underlying w/h is just a number.
Pinning a frame’s color scheme
A frame can carry scheme: "light" or scheme: "dark". A pinned frame always renders in that scheme; a frame without one follows the canvas’s light/dark toggle. Pins are how a board shows the light and dark versions of one screen side by side, as the sample does above, still one tree, now rendered against both palettes. Agents set or clear a pin with update_frame (patch: { scheme: "dark" }, or null to clear). A screenshot without an explicit mode follows the pin of the frames hosting the screen, and asks for a mode when those frames disagree.
Why not Pages and Variants
Because a viewport is not a variant. Tools built on Pages → Variants make you fork a page to see it at another size, then keep the forks in sync by hand (or by hoping). Velloo splits the axis:
- Same content, different size → multiple frames of one screen. Sync is structural, not a feature.
- Different content (a genuinely distinct layout at a breakpoint, not the same tree reflowed) → a separate screen, with its own frames.
The model keeps those two cases from collapsing into one confusing axis. When you want a mobile layout that reorders sections or drops a sidebar entirely, make a new screen; when you just want to see how the responsive tree behaves at 390px, add a frame.
Boards per flow
A folder hosts many boards, and the same screen can appear on several of them (and in several frames on one board): a pricing screen might sit on both the Marketing board and a Checkout-flow board, staying identical everywhere.
Two kinds of grouping keep things tidy. Within a board, a frame can carry a group id that refers to one of the board’s groups, each a name plus an optional color; set it with add_frame or update_frame. Across boards, the sidebar can file boards into groups, set with add_board or update_board (patch: { group }).
A board you’re done with doesn’t have to be deleted. update_board with patch: { archived: true } archives it: the board drops out of the sidebar, list_boards (unless you pass includeArchived), and a default publish, but it stays on disk untouched and fully editable, and archived: false brings it back. remove_board, by contrast, is permanent.
Board layout (positions, sizes, groups, schemes, notes) is canvas-only data. It never appears in emitted code.
The folder is pure data
A design folder is JSON made of real components. There are no components/*.tsx files inside it; components come from a provider resolved per screen, embedded in the Velloo binary or installed in your app depending on the framework. That’s what makes a design diffable, reviewable, and portable: it’s data, not a rendered artifact you translate back into code.
Components that only exist in your app are mostly found for you: repository components are discovered from what the app renders and drawn from its own installed copy. Beyond those, customization happens through snippets (reusable, parameterized subtrees you define once and instantiate anywhere) and, for the leaf cases neither covers, extensions. Want a custom Button treatment? Wrap the provider’s Button in a snippet; don’t fork a component into the folder.