Get started

Make your first diagram

pronounced like skemd /skɛmd/

Write a few lines of text and turn them into accessible, deterministic SVG for circuits or UML.

sheet
1 / 11
rev
v0.2
sections
2
compiled
2

01 / InstallCompile text into SVG

We treat a schematic the way a compiler treats source, so the first step is the one every compiler asks: install it, then hand it a string.

npm install @schemd/core

A schemd document is bounded before it is parsed. parseSchematicFence validates the fence header — the canvas dimensions and an accessible title — and compileSchematic walks the declarations into deterministic SVG:

import { compileSchematic, parseSchematicFence } from '@schemd/core';

const fence = parseSchematicFence('schemd bounds="640x260" title="Sensor input"')!;

const { svg, document, metrics } = compileSchematic(source, fence);

All of this runs on a server or during a build — no DOM, no font-loading pass, no layout library. The compiler reserves space from the bounds we declared, so the page never reflows once the vector lands.

Consider a sensor front-end: a source, a series resistor, a shunt capacitor to ground. The diagram beside this is not a drawing of that network — it is that network, compiled. Because the topology is explicit, we can reason about it: the resistor and capacitor form a first-order low-pass filter with cutoff

fc=12πRCf_c = \frac{1}{2\pi R C}

which, for R=10kΩR = 10\,\text{k}\Omega and C=100nFC = 100\,\text{nF}, lands near fc159 Hzf_c \approx 159\ \text{Hz}.

port:VIN "Sensor" at (60, 150) #blue
resistor:R1 "10 k\Omega" at (245, 150) #amber
capacitor:C1 "100 nF" at (455, 150) #cyan
port:ADC "ADC" at (660, 150) #emerald

VIN.out -> R1.in #blue [line]
R1.out -> C1.in #amber [ortho]
C1.out -> ADC.in #emerald [line marker-end=arrow]
compiled by @schemd/core → shown in the rail

02 / MarkdownKeep Markdown in your app

Core ships no Markdown parser, and the omission is deliberate: the boundary between parse my prose and compile my diagrams belongs to us. Keep the parser on the server, recognize schemd fences, forward only the body.

import { compileSchematic, parseSchematicFence } from '@schemd/core';

function renderSchemdFence(body: string, info: string) {
	const fence = parseSchematicFence(info);
	if (!fence) return undefined;

	return compileSchematic(body, fence).svg;
}

Wire that one function into Marked, markdown-it, unified, or a build step of our own. This site uses exactly that boundary — not a byte of compiler or Markdown code reaches the browser. Note that the grammar which drew the circuit above also describes structure; below it holds a two-class model instead of a filter.

class:User "User" at (200, 180) #slate [attributes="- id: UUID; + email: string" operations="+ save(): void"]
class:Admin "Admin" at (560, 180) #blue [operations="+ suspend(user): void"]

Admin.left -> User.right #blue [ortho generalization]
compiled by @schemd/core → shown in the rail