Skip to content

The Blueprint DSL

When an agent designs in Brilliant, it isn't clicking around or emitting giant JSON blobs. It writes Blueprint: a compact, line-oriented text language built for exactly this job. This page explains what Blueprint is, why it exists, and how to read it, so you can follow along with what your agent is actually doing.

You don't have to write Blueprint yourself. It's here for the curious and for anyone building on top of Brilliant's MCP server.

What Blueprint is

Blueprint is a tiny domain-specific language for describing 2D design. It has a handful of element types (frames, text, vectors, lines, icons), properties (position, size, fill, stroke, radius, effects, auto layout), and references that let one line point at another. One element per line, children indented under their parent. The whole grammar fits on a page.

Agents create and modify a canvas by sending Blueprint to Brilliant's create_modify_elements tool. Everything that lands is a real, native, editable element: the same objects you'd get by drawing them by hand, ready to keep editing in the app.

Why a DSL instead of JSON or raw tool calls

A design isn't a static document. It's a live graph with an auto-layout solver, hit testing, selection state, and z-order, all updating on every change. Two obvious representations both fall short for an agent driving that graph:

  • Raw JSON is explicit and verbose, built for machines rather than for an agent's working memory. A single card with a few children runs to hundreds of lines, and the agent spends most of its context on field names instead of design decisions.

  • One tool call per operation (move, resize, fill, group, and a few hundred more) makes even a small card five or six calls, and the agent has to learn which call does what before it can start.

Blueprint is the third option, and it plays to what agents are already great at: reading and writing unfamiliar programming languages. It wins on four fronts:

  • Density. A card that's ten lines of JSON is one line of Blueprint, at roughly a twentieth of the tokens.

  • Diffs. Line-oriented syntax means an edit is a line, so changes stay small and legible.

  • Streaming. Elements arrive and render in batches as the agent writes them, instead of after one giant payload.

  • Forgiveness. The parser absorbs near-miss syntax and, when something is genuinely wrong, hands back a specific diagnostic and a suggested fix.

The full story behind those tradeoffs is in the Blueprint blog post.

Try it live

Here's Blueprint running on the actual engine, right in the page. The walkthrough below takes you through four small edits on one live card, each a single line, so density, diffs, and streaming stop being claims and become something you can feel.

This card is live Blueprint, compiling on the real engine as you edit it. Work down the four steps and try each edit in the source panel on the left.

Step 1: Edit a value and watch it recompile

The block below is one Blueprint document: the source on the left, the engine's render on the right. Change any value and it redraws in a moment, with no build step and no reload.

Tip

Find "Weekly report" in the source and type your own headline in its place. The title on the card follows along as you type.

Blueprint source
Live canvas
Live on a desktop browser with WebGPU. Edit the source and the canvas recompiles as you type.

Step 2: Reflow it with auto layout

The first line, al(v,g(14),pad(20)), makes the card a vertical stack. g() is the gap between rows and pad() is the padding inside the frame, so those two numbers set all the spacing at once.

Tip

Change g(14) to g(24), then pad(20) to pad(28). Every row breathes out together, because the frame lays its children out for you.

Step 3: Change the shape and the color

Shape rides that same first line: rd(18) rounds the corners and s(440,hug) sets the width. Colors are plain hex values you can swap wherever they appear.

Tip

Round the corners with rd(28), widen the card to s(520,hug), or repaint the blue pill by changing its f[(#0080FF)] to a new hex like f[(#7C3AED)].

Step 4: Add a piece and let layout absorb it

The two stat tiles, "T1" and "T2", are the indented al(v,...) blocks under "Tiles". Add a third and the row shares its width three ways on its own.

Tip

Copy the three "T2" lines (the al(v,...) "T2" frame and its two t(...) children) and paste them right below, keeping the same indentation. A third tile drops into the row and everything rebalances.

That was a handful of Blueprint lines doing real work. The next section reads them, one at a time.

Reading a Blueprint

A few real snippets. You don't need to memorize the syntax to get the shape of it.

A frame with auto layout and children

al(v,g($spacing.lg),pad($spacing.xl)) s(360,hug) f[($color.surface)] rd($radius.md) "Card"
  t("Settings",$font.family,$font.size.lg,sb) f[($color.text.primary)] #title
  t("Manage your preferences",$font.family,$font.size.sm) s(fill,hug) f[($color.text.secondary)] #desc
  al(h,x(c),y(c),g($spacing.sm),pad($spacing.md,$spacing.lg)) s(fill,hug) f[($color.primary)] rd($radius.sm) "Button"
    svg(icon:check) s(16,16) f[($color.on-primary)]
    t("Save",$font.family,$font.size.sm,sb) f[($color.on-primary)]

Line by line:

  • al(v,...) is a vertical auto-layout frame. g() sets the gap between children, pad() the inner padding, s(360,hug) fixes the width at 360 and lets the height hug its content.

  • Indentation is parentage: the two t(...) text lines and the al(h,...) button are children of the card.

  • f[(...)] paints a fill, rd(...) rounds the corners. The trailing "Card" and "Button" are display names.

  • The nested al(h,...) is a horizontal row centered with x(c),y(c); inside it sit a svg(icon:check) icon and a t(...) label.

Everything is a design token

Look again at the values above: $spacing.lg, $color.surface, $font.size.sm. Those aren't literals, they're design tokens resolved through the document's design system.

t("Total",$font.family,$font.size.lg,sb) f[($color.text.primary)]

The font family, the size, and the color all bind to tokens. Change $color.text.primary once and every element that references it updates. That's the same token system you edit by hand in the app, described in the design system docs.

Refs let you edit what you already made

A trailing #name assigns a stable reference to an element. Lead a later line with that same #ref and you're modifying the existing element, not creating a new one.

#desc f[($color.text.primary)]

This repaints the #desc text from the first example to the primary color. There's no need to restate the rest of the element: a leading #ref means "edit this one." To tuck a new element inside an existing frame, an agent adds parent(#ref) to the new line.

Lines double as connectors

For diagrams, line(...) draws a segment, and its endpoint form anchors to two elements, so an arrow follows them as they move.

line(from(#start), to(#end)) intent(flow)

from and to take element refs; intent(flow) picks a sensible route, arrowhead, and stroke in one word. This endpoint form is how Brilliant expresses flowcharts, dependency links, and callouts.

The parser is forgiving by design

Blueprint's compiler is built around in-context learning. It parses, validates before anything touches the canvas, executes valid lines, then lints the result. When a line is slightly off, it absorbs the common cases, and when it can't, it returns a diagnostic with a stable code, a plain-English message, and a concrete fix rather than failing silently. Every response also carries a rendered image of what landed, so the agent sees exactly what it made next to any feedback.

Who this is for

This page is for developers watching an agent work and wondering what those dense lines mean, and for anyone building an MCP integration on top of Brilliant. Agents don't learn Blueprint from a page like this one: Brilliant serves them the grammar automatically over MCP, so they arrive already fluent.

For the complete grammar and the object syntax agents emit, see the public repo at github.com/brilliant-hq/brilliant. To point your own agent at the canvas, start with Connect an Agent.