fd-format

star 3

How to read, write, and modify .fd (Fast Draft) files

khangnghiem By khangnghiem schedule Updated 3/18/2026

name: fd-format description: How to read, write, and modify .fd (Fast Draft) files

FD Format Skill

Overview

The .fd format is a human- and AI-readable text DSL for 2D graphics, layout, and animation. It prioritizes clarity of intent (semantic IDs, constraints, comments) so both humans and AI agents can understand the design. This skill explains how to read and write valid .fd files.

Grammar Reference

Comments

# This is a comment

Imports

Reference other .fd files with namespaced access:

import "components/buttons.fd" as btn
import "shared/tokens.fd" as tokens

rect @hero {
  use: tokens.accent
}

Imported styles and node IDs are prefixed: btn.primary, tokens.accent.

Style Definitions

Named, reusable sets of visual properties:

style <name> {
  fill: <color>
  font: "<family>" <weight> <size>
  corner: <radius>
  opacity: <0-1>
}

Style inheritance via extends::

style dark_card {
  extends: card
  fill: #1A1A2E
}

Node Types

Every visual element is a node with an optional @id:

rect @my_rect {
  w: <width> h: <height>
  fill: <color>
  stroke: <color> <width>
  corner: <radius>
  use: <style_name>
}

ellipse @my_circle {
  w: <rx> h: <ry>
  fill: <color>
}

text @label "Content goes here" {
  font: "<family>" <weight> <size>
  fill: <color>
  use: <style_name>
}

group @container {
  layout: column gap=<px> pad=<px>
  layout: row gap=<px> pad=<px>
  layout: grid cols=<n> gap=<px> pad=<px>

  # Children go here (nested nodes)
  rect @child1 { ... }
  text @child2 "..." { ... }
}

frame @card {
  w: <width> h: <height>
  fill: <color>
  corner: <radius>
  clip: true          # optional — clips children to frame bounds
  pad: <px>           # optional — insets content area (works without layout:)
  layout: column gap=<px> pad=<px>

  # Children go here (nested nodes)
  rect @child { ... }
}

path @drawing {
  # Path data (SVG-like commands) — future
}

Generic Nodes (Placeholders)

Nodes without a shape type — for spec-only requirements, wireframing, and progressive enhancement:

@login_btn {
  spec {
    "Primary CTA — triggers login API call"
    role: button
  }
  fill: #FFFFFF
  corner: 8
}

Generic nodes can be nested inside groups:

group @form {
  layout: column gap=16 pad=32
  @email_input { spec "Email field" }
  @password_input { spec "Password field" }
}

On canvas, generic nodes render as dashed placeholder boxes with the @id label centered. They can later be "upgraded" to a specific type by adding a keyword prefix (e.g., rect @login_btn).

Edges (Connections)

Visual connections between nodes with styled lines, arrowheads, and labels:

edge @login_to_dashboard {
  from: @login_screen
  to: @dashboard
  label: "on success"
  stroke: #10B981 2
  arrow: end            # none | start | end | both
  curve: smooth         # straight | smooth | step
  use: flow_style       # style references work too
}

Edges support spec annotations and use: style references, just like nodes.

Colors

Hex format: #RGB, #RGBA, #RRGGBB, #RRGGBBAA

fill: #6C5CE7
fill: #FF000080    # with alpha

Background Shorthand

bg: #FFF corner=12 shadow=(0,4,20,#0002)

Animations

when :<trigger> {
  fill: <color>
  opacity: <0-1>
  scale: <factor>
  rotate: <degrees>
  ease: <easing> <duration>ms
}

Triggers: :hover, :press, :enter, :<custom> Easing: linear, ease_in, ease_out, ease_in_out, spring Legacy alias: anim is still accepted by the parser but when is canonical.

Constraints (Top-Level)

@node_id -> center_in: canvas
@node_id -> center_in: @other_node
@node_id -> offset: @ref 20, 10
@node_id -> fill_parent: 16

Nodes can also use inline x: / y: for parent-relative positioning (e.g. after drag):

rect @box {
  x: 100
  y: 200
  w: 50 h: 50
}

Annotations (spec)

Structured metadata attached to nodes and edges via spec blocks. Unlike # comments (which are discarded), annotations are parsed, stored on the scene graph, and survive round-trips.

Inline form (single description):


rect @login_btn {
spec "Primary CTA — triggers login API call"
w: 280 h: 48
}

Block form (multiple annotations):


rect @login_btn {
spec {
"Primary CTA — triggers login API call"
accept: "disabled state when fields empty"
status: in_progress
priority: high
tag: auth, mvp
}
w: 280 h: 48
}
Syntax Kind Purpose
"text" Description What this node is/does
accept: "text" Accept Acceptance criterion
status: value Status draft, in_progress, done
priority: value Priority high, medium, low
tag: value Tag Categorization labels

Code Mode — Readability Tips

Prioritize AI-agent readability and accuracy. Token efficiency is a secondary goal. Research shows semantic naming has 2× more impact on AI accuracy than any other factor.

  1. Semantic IDs@login_form not @rect_17; the #1 factor for AI comprehension
  2. Constraints over coordscenter_in: canvas tells agents why, not just where; LLMs reason better with relationships than absolute positions. Inline x: / y: is the acceptable escape hatch for pinned/drag-placed nodes.
  3. Accurate comments# lines help, but wrong comments actively hurt AI; keep them correct or remove them
  4. Spec blocksspec { status: in_progress } gives AI structured metadata it can reliably parse, unlike freeform comments
  5. Style reuseuse: references enforce consistency; consistent codebases produce better AI-generated code
  6. Shorthand is finew: / h: / #FFF are unambiguous in context, no need to expand
  7. Content-first ordering — inside node blocks, the emitter outputs properties in this order: spec → structure (layout, dimensions) → children → appearance (fill, stroke, corner, font) → position (x/y) → animations. This lets non-tech users scan the content tree before styling details.
  8. Font weight namesfont: "Inter" bold 18 instead of font: "Inter" 700 18; the parser accepts both forms
  9. Named colorsfill: purple, fill: blue etc. are accepted (17 Tailwind palette colors); hex always works too
  10. Property aliasesbackground: / color: → fill, rounded: / radius: → corner; the emitter uses canonical names
  11. Dimension unitsw: 320px is accepted; the px suffix is cosmetic and stripped by the parser
  12. Always use paddingpad: 12 on frames adds breathing room between edges and content; layout: column pad=16 on managed layouts; never place children flush against frame edges
  13. Prefer managed layouts — use layout: column gap=8 pad=16 instead of manual x: / y: for stacked elements; it's more maintainable and responsive

Example: Complete Card


style body { font: "Inter" 14; fill: #333 }
style accent { fill: #6C5CE7 }

group @card {
layout: column gap=12 pad=20
bg: #FFF corner=8 shadow=(0,2,8,#0001)

text @heading "Dashboard" { font: "Inter" 600 20; fill: #111 }
text @desc "Overview of metrics" { use: body }

rect @cta {
w: 180 h: 40
corner: 8
use: accent
text "View Details" { font: "Inter" 500 14; fill: #FFF }
when :hover { scale: 1.03; ease: spring 200ms }
}
}

@card -> center_in: canvas

Crate Locations

  • Parser: crates/fd-core/src/parser.rs
  • Emitter: crates/fd-core/src/emitter.rs
  • Data model: crates/fd-core/src/model.rs
  • Layout solver: crates/fd-core/src/layout.rs

Install via CLI
npx skills add https://github.com/khangnghiem/fast-draft --skill fd-format
Repository Details
star Stars 3
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator