DEV Community

Cover image for XMarkdown: Markdown charts, diagrams and math for AI reports
Muhammad Haseeb Iqbal
Muhammad Haseeb Iqbal

Posted on AI-assisted

XMarkdown: Markdown charts, diagrams and math for AI reports

Abstract

XMarkdown is an open-source Markdown renderer for documents that need charts, diagrams, equations, interactive controls, and validated SVG. The first MVP keeps familiar Markdown for prose and adds optional blocks for visual content. This article explains why I built it, how its shared Node/browser engine works, and what the initial measurements show. One complete demo used 208 source tokens against 1,219 for compact HTML/SVG; an extended streaming workload achieved a 5.48× improvement over repeatedly rendering growing prefixes. These results suggest a useful direction for AI-generated reports, with model generation and user studies still to come.

Try the live playground · Explore the GitHub repository · Install from npm

1. Why I started with Markdown

I like Markdown. I can read the source, edit a paragraph, and understand the document without opening a browser.

But I also want a report to contain a chart when a chart helps. Or a diagram when explaining a system through paragraphs becomes awkward. Seeing AI tools produce HTML artifacts for this kind of content made me wonder how much of that output really needed to be written again for each document.

A bar chart needs data and labels. Its author usually shouldn't have to decide the position of every tick mark or repeat the same SVG attributes. An AI generating the report has the same problem: it can spend a lot of its output describing rendering details that a library already knows how to produce.

That was the starting question for XMarkdown: could a document stay readable as Markdown while describing enough visual content for a renderer to do the rest?

I wanted the answer to include installation and real usage. The project now has a published npm package, a React component, and a live site with 18 examples, including capacity planning, release workflows, mathematical derivations, drawing, and a layered SVG architecture scene.

The site uses the actual XMarkdown engine. Its saved examples are rendered through the Node entry; edits run through the browser entry. Both share the same core.

2. Design: describe the content, reuse the rendering

XMarkdown uses markdown-it for ordinary Markdown. Headings, emphasis, lists, quotes, links, code fences, tables, and strikethrough keep their usual syntax.

The extensions are optional. A document can add an x-chart block for a chart or an x-diagram block for a directed graph. Those blocks contain data checked by the renderer before it produces the visual.

For example, this is a complete chart specification:

~~~x-chart
{
  "type": "bar",
  "title": "Requests per day",
  "description": "Monday 24; Tuesday 31; Wednesday 28.",
  "x": "day",
  "y": "requests",
  "data": [
    {"day": "Mon", "requests": 24},
    {"day": "Tue", "requests": 31},
    {"day": "Wed", "requests": 28}
  ]
}
~~~
Enter fullscreen mode Exit fullscreen mode

The renderer supplies the SVG geometry, axes, and labels. A description also gives the document a textual explanation of the visual.

DEV displays these extensions as code. To see the graphics and interactions, paste them into the XMarkdown playground or use the package in your application.

This format has a cost: someone has to learn its schemas, and the host has to load a renderer. The benefit is that those schemas and rendering rules can be reused across documents.

Implementation matrix

Layer Existing processor What XMarkdown adds
Markdown markdown-it Extension rules, callouts, feature dispatch, checked links
Equations KaTeX Bounded input, lazy loading, MathML output, caching
Imported SVG saxes XML parser Allowed elements and attributes, local references, size limits
Charts and diagrams Custom JavaScript Data validation, geometry, SVG templates and labels
Controls and drawing Custom JavaScript Expression interpreter, native controls, shared widget runtime
Streaming Custom JavaScript Partial updates, bounded caching, final complete render

The implementation is JavaScript. Node, the browser API, and React share it. Other server languages can use a Node/HTTP bridge; separate Python, Go, or Rust parsers haven't shipped.

3. What the first MVP supports

Capability Current implementation Scope
Markdown prose Familiar Markdown plus tables and strikethrough Raw HTML is escaped; images currently display alt text
Callouts Notes, tips, warnings :::tip[Title] and related callouts
Charts SVG bar and line charts Up to 256 data rows
Diagrams Directed graphs with automatic placement Up to 32 nodes and 128 edges; basic routing
Mathematics Inline and display equations Supported TeX compiled by KaTeX into MathML
Calculated controls Ranges, numbers, toggles, selects, outputs Bounded expressions with arithmetic, comparisons and selected functions
Drawing SVG strokes, undo and clear Pointer interaction with stroke and point limits
SVG scenes Paths, groups, gradients, masks, clipping, patterns, symbols Validated static scene format
SVG imports Existing SVG markup inside an x-svg block Supported static subset, without linked resource fetching
Streaming Provisional previews and final rendering Bounded cache; a complete Markdown parse at completion
Integration Node, browser, React, TypeScript Next.js and Vue recipes; further adapters planned

The larger examples are where this becomes interesting. The architecture scene contains three layers, seven service cards, storage endpoints, curved connectors, reusable symbols, gradients, a grid pattern, masking, and clipping.

There is also a multi-input capacity model and a live React demo. They give a better picture of the renderer than a single chart does.

4. A complete React example

In an existing React 18+ project, install the package:

npm install xmarkdown-renderer
Enter fullscreen mode Exit fullscreen mode

Use Node.js 22+ for the supported development/runtime setup. The current package version is 0.2.1, and TypeScript declarations are included.

Here is an editable report with prose, a chart, a diagram, an equation, and a live control:

import { useState } from 'react';
import { XMarkdown } from 'xmarkdown-renderer/react';
import 'xmarkdown-renderer/styles.css';

const initialSource = String.raw`# Service capacity report

Requests increased this week. **Here is the daily breakdown.**

~~~x-chart
{
  "type": "bar",
  "title": "Requests per day",
  "description": "Monday 24; Tuesday 31; Wednesday 28.",
  "x": "day",
  "y": "requests",
  "data": [
    {"day": "Mon", "requests": 24},
    {"day": "Tue", "requests": 31},
    {"day": "Wed", "requests": 28}
  ]
}
~~~

~~~x-diagram
{
  "title": "Request pipeline",
  "description": "Validate each request before storing it.",
  "nodes": [
    {"id": "validate", "label": "Validate"},
    {"id": "store", "label": "Store"}
  ],
  "edges": [["validate", "store"]]
}
~~~

For this queue model, stable arrival requires $\lambda < \mu$.

~~~x-widget
{
  "type": "controls",
  "title": "Try a different arrival rate",
  "description": "Compare arrival with a capacity of 100 requests/min.",
  "inputs": [
    {"id": "rate", "label": "Requests/min", "type": "range",
     "min": 0, "max": 180, "value": 80}
  ],
  "outputs": [
    {"id": "growth", "label": "Queue growth",
     "expr": "max(0, rate - 100)", "unit": "requests/min"}
  ]
}
~~~

:::tip[Try it]
Edit a chart value, or move the slider to test the capacity assumption.
:::
`;

export default function App() {
  const [source, setSource] = useState(initialSource);
  const [error, setError] = useState(null);

  return (
    <main>
      <label htmlFor="report">Edit the report</label>
      <textarea
        id="report"
        rows={12}
        value={source}
        onChange={event => setSource(event.target.value)}
      />
      {error && <p role="alert">{error.message}</p>}
      <XMarkdown
        source={source}
        onError={setError}
        onDiagnostics={() => setError(null)}
      />
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

The ~~~ fences are standard Markdown and are convenient inside a JavaScript template literal. Backtick fences work as well. String.raw keeps the TeX backslashes intact.

The stylesheet supplies shared presentation for charts, diagrams, callouts, and widgets. Import it once in the application; document authors don't have to write CSS for each report. Hosts can override the theme or supply equivalent styles.

The browser parser prepares optional processors when needed. React mounts supported widget behavior automatically. For server rendering, the component can also accept a parsed document; the integration guide includes that path.

5. Evaluation method

The initial evaluation asks two different questions:

  1. How much author-written source does XMarkdown need compared with HTML/SVG for the supplied examples?
  2. How does parsing and rendering behave with ordinary documents, cached graphics, and growing streams?

For the source comparison, the repository contains 12 measured example pairs. The complete playground fixture is used for the main comparison. Its rendered body supplies literal HTML/SVG; a compact variant rounds numeric SVG attributes to 0.001 units while keeping the elements, labels, descriptions, and axes.

There is also an HTML baseline using shared graphics components. That baseline reuses the same validation and graphics renderer, so it tests how much of the saving comes from reusable components.

The tables below use the o200k_base tokenizer. The comparison artifacts also retain cl100k_base counts. Source has LF line endings. Shared runtimes and format instructions are excluded from the document-fragment counts; the separate format guide contains 383 o200k_base tokens.

Processing measurements ran locally on Windows with Node.js 22.22.0. The feature benchmark used seven timed trials with rotating condition order and warmups, reporting median time per document. An empty extension cache still had dependencies loaded. First-use observations include additional startup work and are reported separately in the benchmark.

These measurements were recorded for v0.2.0 on 8 November 2026 with markdown-it 14.3.3. The current 0.2.1 package uses markdown-it 15.0.3. Keeping the measurement version explicit makes the results reproducible.

6. Results: source size

The same complete document, five representations

Representation UTF-8 bytes Source tokens
XMarkdown 839 208
Literal HTML/SVG 3,610 1,259
Compact HTML/SVG 3,492 1,219
Standalone styled HTML 4,914 1,689
HTML with shared graphics components 1,044 261

For this fixture, XMarkdown used 82.9% fewer source tokens than compact HTML/SVG, and 20.3% fewer than shared-component HTML.

The component baseline is encouraging in its own way. It shows how much can be gained by moving repeated geometry into reusable software. Markdown then gives the surrounding prose a compact, familiar representation.

Results across the supplied examples

Example XMarkdown tokens Compact HTML tokens XMarkdown source reduction
Headings, emphasis and lists 20 42 52.4%
Comparison table 23 68 66.2%
Callout 19 47 59.6%
Chart with axes and labels 79 635 87.6%
Automatically placed pipeline 79 494 84.0%
Branching dependency graph 128 727 82.4%
Complete playground document 208 1,219 82.9%
Mathematical notation 14 75 81.3%
Queue what-if control 112 148 24.3%
Drawing application 52 317 83.6%
Complex vector scene 844 978 13.7%
Existing SVG import 225 219 -2.7%

The raw SVG import needs a fence around existing markup, so it uses slightly more source. A custom vector scene still has to describe its geometry. The largest reductions appear where a chart or graph can be described through data that the renderer understands.

These comparisons aren't measurements of an AI provider's billed usage or generation speed. Those require counting prompts, format examples, output, and repair turns in an actual model experiment. The source results give us a concrete starting point for that experiment.

Full source comparisons and paired examples

7. Results: parsing, caching and streaming

Early in the project, I asked for a parser that could be 50 times faster than a regular Markdown parser. Benchmarking helped turn that ambition into a more useful question: where can we avoid doing the same work again?

For a complete plain-prose document of 6,320 characters, bare markdown-it took 0.259 ms and XMarkdown took 0.268 ms. Adding document features has a processing cost. The current data doesn't support a general 50× parsing claim.

The extended workload shows where reuse can help:

Extended document workload Median parse + render time
Empty extension cache, dependencies loaded 0.652 ms
Extension cache disabled 0.508 ms
Repeated document with cached extensions 0.075 ms

In a separate streaming comparison, XMarkdown achieved 5.48× faster processing than repeatedly parsing and rendering the growing full prefix for that extended fixture.

Streaming emits provisional section updates and performs a complete parse at the end. The final pass keeps full-document Markdown semantics, including references. The bounded cache can reuse validated specifications and generated geometry, with SVG IDs rewritten for each placement.

The benchmark also includes cases where streaming helps less: the short math fixture measured 0.78× against repeated-prefix rendering. Document shape and chunking affect the result.

Browser layout, painting, network traffic, model generation, and total heap memory aren't part of these timing results. The next evaluation should measure the whole path from generated source to a usable document.

To reproduce the comparisons from a repository checkout with dependencies installed:

npm run compare
npm run bench:features
Enter fullscreen mode Exit fullscreen mode

8. Complex SVG and the document boundary

I wanted the format to handle more than a few preset chart types. The MVP therefore has a structured SVG scene format and can import supported existing SVG markup.

Here is a small import that uses a gradient and a curved path:

~~~x-svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 180">
  <title>Capacity curve</title>
  <desc>A cubic curve drawn with a blue-to-green gradient.</desc>
  <defs>
    <linearGradient id="capacity" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" stop-color="#38bdf8" />
      <stop offset="100%" stop-color="#a3e635" />
    </linearGradient>
  </defs>
  <path d="M20 140C110 0 290 0 380 140"
        fill="none" stroke="url(#capacity)" stroke-width="4" />
</svg>
~~~
Enter fullscreen mode Exit fullscreen mode

The scene processor supports paths, groups, gradients, masks, clipping, patterns, markers, and reusable symbols. Both scene data and imported XML go through validation. Imports can need cleanup when an SVG editor adds unsupported metadata or styling.

Scripts, event handlers, external resources, foreignObject, filters, and animation are outside the current static SVG profile. Control expressions use a bounded interpreter without eval; authors can't add arbitrary JavaScript to a document.

Those choices give the format a defined document boundary. Validation still needs further fuzzing and independent security review. Applications handling hostile input should follow the security guide and apply isolation and resource budgets at the host level.

9. What this could change for AI products

The opportunity I see is a shared document layer for generated reports. A model could write the explanation and the data for supported visuals; the application could control presentation through a renderer it tests and maintains.

That could be useful for:

  • AI report builders that repeatedly generate prose alongside the same chart types.
  • Developer tools that explain request flows, dependencies, and architecture.
  • Internal analysis tools that put equations and what-if controls next to a written argument.
  • Education products that need editable explanations with mathematical notation and diagrams.

Teams at Anthropic working on Claude artifacts, and teams at OpenAI building AI interfaces, are audiences I'd like this proposal to reach. I'd also like to hear from developers building smaller AI products where document output is a daily integration problem. These are invitations to evaluate the approach; the project has no affiliation with either company.

For a company considering adoption, I'd start with a bounded report workflow that already uses supported chart and diagram types. Compare equivalent HTML/component output against XMarkdown, including:

  1. Total input and output tokens, with format instructions included.
  2. Valid-output rate and the number of repair turns.
  3. Generation, validation, rendering, and interaction latency.
  4. How accurately people understand and edit the result.

If those trials show consistent benefits, a compact document format with interoperable renderers could change how AI applications deliver rich explanations. That is the possibility worth building toward. XMarkdown is an installable first MVP that lets us start testing it now.

10. Where the project stands

The repository includes CI for Node 22 and 24, TypeScript declarations, tests, a changelog, and contribution and security policies. Node/browser integration checks and a live React application exercise the shared engine.

There is still work to do on graph routing, more chart types, a browser worker, accessibility coverage, and complete framework applications. Mermaid rendering, task-list checkboxes, and external-image handling are also future work. APIs and syntax can change before 1.0.

For a document containing only prose, I'd use regular Markdown. For unrestricted page layouts or custom applications, HTML and JavaScript remain the flexible option. XMarkdown is most useful when a report needs familiar text plus visual features that can be described and rendered consistently.

You can try it without cloning the project: npm install xmarkdown-renderer in your existing application, or open the playground.

If you try the renderer, an example that breaks, a missing chart type, or an awkward integration is useful feedback. Open an issue or contribute a change. If you'd like to follow the work, a star on the GitHub repository helps you keep track of it.

Source material

Top comments (0)