Generate clean SVG diagrams (flowchart, tree, architecture, sequence, quadrant, gantt, state machine, ER, timeline, swimlane, bubble chart) from a markdown string or a JSON config via fig(). Auto-layout, zero coordinates needed. Works in browser and Node.js.
Resources
8Install
npx skillscat add hustcc/ai-figure Install via the SkillsCat registry.
ai-figure Skill
Generates self-contained SVG diagrams. No coordinates needed — layout is computed automatically.
import { fig } from 'ai-figure';
// Markdown string (preferred — compact, streaming-safe)
const svg = fig(`
figure flow
direction: LR
palette: antv
title: CI Pipeline
code[Write Code] --> test{Tests Pass?}
test --> build[Build Image]: yes
test --> fix((Fix Issues)): no
fix --> code
build --> deploy[/Deploy/]
group Pipeline: code, test, build
`);
// JSON config object (programmatic / strongly-typed)
const svg2 = fig({ figure: 'flow', nodes: [...], edges: [...] });
// DOM: document.getElementById('chart').innerHTML = svg;
// Node.js: fs.writeFileSync('chart.svg', svg);fig() accepts either a markdown string or a JSON config. When given a string it never throws — partial or empty input (e.g. during AI streaming) returns a valid empty SVG that fills in progressively.
Markdown syntax
First line must be: figure <type>
Config lines use key: value syntax. Data lines use diagram-specific patterns.
| Key | Values | Default |
|---|---|---|
type |
flow tree arch sequence quadrant gantt state er timeline swimlane bubble |
required |
direction |
TB LR |
TB |
theme |
light dark |
light |
palette |
default antv drawio figma vega mono-blue mono-green mono-purple mono-orange |
default |
Lines starting with %% are comments. title: and subtitle: work in all types.
Node notation (flow / tree / arch)
| Notation | Shape |
|---|---|
id[label] |
process (rectangle) |
id{label} |
decision (diamond) |
id((label)) |
terminal (pill) |
id[/label/] |
io (parallelogram) |
id |
process, id used as label |
flow
figure flow
direction: LR
palette: antv
title: My Flow
A[Source] --> B[Target] %% simple edge
A --> B[Target]: label %% labeled edge
group Name: id1, id2, id3 %% logical group (dashed border)tree
figure tree
direction: LR
title: Org Chart
root[Root]
root --> child[Child]
child --> leaf[Leaf]arch
figure arch
direction: TB
palette: antv
title: Web Stack
layer Frontend
ui[React App]
assets[Static Assets]
layer Backend
api[REST API]
auth[Auth Service]
layer Data
db[PostgreSQL]sequence
figure sequence
title: Login
actors: Browser, API, DB %% optional; inferred from messages if omitted
Browser -> API: POST /login %% solid arrow
API --> Browser: 200 OK %% dashed return arrowquadrant
figure quadrant
title: Priority
x-axis Effort: Low .. High
y-axis Value: Low .. High
quadrant-1: Quick Wins %% top-left
quadrant-2: Strategic %% top-right
quadrant-3: Low Prio %% bottom-left
quadrant-4: Long Shots %% bottom-right
Feature A: 0.2, 0.9 %% label: x, y (x/y in [0,1])gantt
figure gantt
title: Q1 Roadmap
section Design
Wireframes: t1, 2025-01-06, 2025-01-24 %% label: id, start, end
Mockups: t2, 2025-01-25, 2025-02-07
section Dev
Frontend: t3, 2025-02-03, 2025-02-28
milestone: Launch, 2025-03-01- Task format:
<label>: <id>, <yyyy-mm-dd>, <yyyy-mm-dd>— id is required, even if you don't reference it end≥start;sectiongroups tasks under a bold header;milestone: <label>, <date>marks a point in time
state
figure state
title: Order Status
idle[Idle]
processing[Processing]
accent: failed %% mark as accent/focal state
start --> idle %% start pseudo-state
idle --> processing: order placed
processing --> end: shipped
processing --> failed: error
failed --> idle: retryid[label]— normal state (rounded rectangle)start/end— reserved pseudo-state ids (filled circle / ringed circle)id --> id2: event— transition with optional labelaccent: id— mark a state as the focal/error state (max 1–2)
er
figure er
title: Blog Schema
entity User
id pk: uuid
email: text
entity Post
id pk: uuid
author_id fk: uuid
title: text
User --> Post: writesentity Name— declare an entity box (name used as id and label)- Fields:
name pk: type(primary key),name fk: type(foreign key),name: type, or barename A --> B: label— relationship line with optional labelaccent: EntityNameto mark the aggregate root entity
timeline
figure timeline
title: Product History
2020-01-15: v1.0 Launch milestone %% major milestone (larger accent dot)
2021-06-01: v1.5 Improvements
2022-03-10: v2.0 Redesign milestone
2023-11-01: v3.0 AI Features- Lines:
yyyy-mm-dd: labeloryyyy-mm-dd: label milestone - Events are sorted chronologically and spaced proportionally on a horizontal axis
- Labels alternate above and below the baseline to reduce collision
swimlane
figure swimlane
title: Order Flow
section Customer
order[Place Order]
pay[Confirm Payment]
section Warehouse
receive[Receive Order]
pack[Pack Items]
section Shipping
ship[Ship Package]
order --> pay
pay --> receive
receive --> pack
pack --> shipsection LaneName— declares a new lane; subsequent node lines belong to itid[Node Label]— node declaration inside the current laneA --> BorA --> B: label— directed edges (may cross lanes)
bubble
figure bubble
title: Market Analysis
%% label: value (positive number)
Product A: 75
Product B: 50
Product C: 85- Data lines:
Label: value— any positive number; bubble area is proportional to value - Positions computed automatically; no coordinates needed
JSON config (fig(options))
Same result as markdown but typed. Use when building diagrams programmatically.
All options share: title?, subtitle?, theme?: 'light'|'dark', palette?: string|string[], and (where applicable) direction?: 'TB'|'LR'.
// flow
{ figure: 'flow', nodes: FlowNode[], edges: FlowEdge[], groups?: FlowGroup[] }
// FlowNode: { id, label, type?: 'process'|'decision'|'terminal'|'io' }
// FlowEdge: { from, to, label? }
// FlowGroup: { id, label, nodes: string[] }
// tree
{ figure: 'tree', nodes: TreeNode[] }
// TreeNode: { id, label, parent? }
// arch
{ figure: 'arch', layers: ArchLayer[] }
// ArchLayer: { id, label, nodes: { id, label }[] }
// sequence
{ figure: 'sequence', actors: string[], messages: SeqMessage[] }
// SeqMessage: { from, to, label?, style?: 'solid'|'return' }
// quadrant — quadrants: [top-left, top-right, bottom-left, bottom-right]; x=0 left, y=0 bottom
{ figure: 'quadrant', xAxis: { label, min, max }, yAxis: { label, min, max },
quadrants: [string, string, string, string], points: QuadrantPoint[] }
// QuadrantPoint: { id, label, x, y } x/y in [0,1]
// gantt
{ figure: 'gantt', tasks: GanttTask[], milestones?: GanttMilestone[] }
// GanttTask: { id, label, start, end, groupId? } start/end: 'yyyy-mm-dd'
// GanttMilestone: { date, label }
// state
{ figure: 'state', nodes: StateNode[], transitions: StateTransition[] }
// StateNode: { id, label, type?: 'state'|'start'|'end', accent?: boolean }
// StateTransition: { from, to, label? }
// er
{ figure: 'er', entities: ErEntity[], relations: ErRelation[] }
// ErEntity: { id, label, fields: ErField[], accent?: boolean }
// ErField: { name, type?, key?: 'pk'|'fk' }
// ErRelation: { from, to, label? }
// timeline
{ figure: 'timeline', events: TimelineEvent[] }
// TimelineEvent: { id, label, date, milestone? } date: 'yyyy-mm-dd'
// swimlane
{ figure: 'swimlane', lanes: string[], nodes: SwimlaneNode[], edges: SwimlaneEdge[] }
// SwimlaneNode: { id, label, lane, type? } lane = one of lanes[]
// SwimlaneEdge: { from, to, label? }
// bubble
{ figure: 'bubble', items: BubbleItem[] }
// BubbleItem: { label, value } — value is a positive number; area proportional to value