TikZpen is a free browser editor where you draw shapes, text and Bézier paths on a canvas and get ready-to-paste LaTeX TikZ code in real time. In September 2026 I rebuilt it from scratch. This is why, and what I learned about building a tool where correctness is the whole point.
The old model fought the output#
The first version worked, but its internal model was a generic canvas scene graph, and TikZ is not a canvas. Every feature meant translating between two ideas of what a drawing is, and the translation was where the bugs lived: a shape could look right on screen and export subtly different TikZ.
TikZ source
% libraries: arrows.meta, positioning, decorations.pathmorphing
\begin{tikzpicture}[font=\sffamily\small, >={Stealth}, node distance=12mm,
box/.style={draw, rounded corners, minimum height=9mm, minimum width=2.5cm, align=center}]
\node[box, fill=red!10] (canvas) {Canvas\\scene graph};
\node[box, right=36mm of canvas, fill=red!10] (tikz) {TikZ};
\draw[->, thick, red!60!black, decorate, decoration={snake, amplitude=0.6mm, segment length=4mm}] (canvas) -- node[above=2mm, red!60!black] {lossy translation} (tikz);
\node[below=8mm of canvas, align=center, font=\sffamily\footnotesize, text=red!60!black] {two models of "a drawing"};
\end{tikzpicture}Make the model TikZ-shaped#
In the rebuild the native document format is TikZ-shaped: layers, nodes, paths and styles map straight onto what TikZ can say. Export stops being a conversion and becomes a per-node emit. The rule I wrote down for myself: if a field doesn't correspond to something TikZ can express, it doesn't belong in the model.
TikZ source
% libraries: arrows.meta, positioning
\begin{tikzpicture}[font=\sffamily\small, >={Stealth}, node distance=10mm,
box/.style={draw, rounded corners, minimum height=9mm, minimum width=2.6cm, align=center},
doc/.style={box, fill=orange!25, minimum width=3.4cm, minimum height=12mm}]
\node[doc] (doc) {Document\\layers, nodes, paths, styles};
\node[box, above right=2mm and 20mm of doc] (canvas) {Live canvas};
\node[box, right=20mm of doc] (svg) {SVG export};
\node[box, below right=2mm and 20mm of doc] (tikz) {TikZ export};
\node[box, below=10mm of doc] (imp) {Import\\TikZ and SVG};
\draw[->] (doc) -- node[above, sloped, font=\sffamily\scriptsize] {one renderer} (canvas);
\draw[->] (doc) -- node[above, font=\sffamily\scriptsize] {same renderer} (svg);
\draw[->] (doc) -- node[below, sloped, font=\sffamily\scriptsize] {per-node emit} (tikz);
\draw[->] (imp) -- (doc);
\end{tikzpicture}One renderer, no drift#
The live canvas and the SVG export use the same renderer: a single function turns a document into a React SVG tree. If the viewport and the export can't disagree about what a shape looks like, a whole class of "looks different once exported" bugs simply can't exist.
Proving it with a fidelity check#
"The export looks right" isn't a test. So the exported TikZ is sent to a compile service and the compiled result is compared with the editor's own rendering of the document, a check that the TikZ really reproduces the drawing one to one. I run the same harness in development, and it exposed real renderer bugs that looking at the screen had missed.
TikZ source
% libraries: arrows.meta, positioning
\begin{tikzpicture}[font=\sffamily\small, >={Stealth}, node distance=9mm,
box/.style={draw, rounded corners, minimum height=9mm, minimum width=2.4cm, align=center}]
\node[box] (doc) {Document};
\node[box, right=14mm of doc] (tikz) {Exported\\TikZ};
\node[box, right=14mm of tikz] (comp) {LaTeX compile\\service};
\node[box, right=14mm of comp] (img) {Compiled\\image};
\node[box, below=12mm of doc] (ren) {Editor's own\\rendering};
\node[box, fill=orange!25, below=12mm of img] (cmp) {Compare};
\draw[->] (doc) -- (tikz); \draw[->] (tikz) -- (comp); \draw[->] (comp) -- (img);
\draw[->] (doc) -- (ren);
\draw[->] (img) -- (cmp);
\draw[->] (ren) -- (cmp);
\node[right=3mm of cmp, font=\sffamily\footnotesize, align=left] {match = export is faithful\\mismatch = a bug to fix};
\end{tikzpicture}Around that check:
- Golden fixtures. Export output is pinned with
.tikzand.svgfixtures, and the pure logic (model, geometry, commands, export, import) is covered by Jest tests. - Undo and redo as commands. Every edit is an invertible command on a bounded history, which keeps undo reliable across multi-select, groups and path editing.
- Import as well as export. TikZ and SVG importers let people bring existing work in, with warnings for anything that can't be represented rather than silently dropping it.
Product details that mattered#
Autosave to the browser for everyone, optional cloud documents with version history for signed-in users, snapping, node editing, layers and groups, and a mobile layout that stacks the panels. The old editor is still reachable (noindexed) for old bookmarks, but all new work goes into the rebuild.
Stack#
TypeScript, React, Next.js, a Zustand store, IndexedDB and Firestore for persistence, a containerised LaTeX compile service, Jest.
Takeaway#
When correctness is the product, shape the data model after the output format, share one renderer between everything that draws, and let an automated check tell you when you're wrong. You can try the editor at TikZpen, and if you're new to TikZ, this tutorial covers the language itself.