Skip to content
chartlet
MITDocumentation
Rust CLI · Astro integration · Open source

JSON in,
finished SVG out

chartlet compiles a small JSON specification into a finished chart at build time. Nothing of it runs in the browser: no chart JavaScript, no hydration, no layout shift.

0 KB
Chart JavaScript in the browser
SVG · HTML
Output formats
byte-identical
for the same specification
Terminal
$ cargo install chartlet --version 0.1.0-alpha.3
$ chartlet render monthly-revenue.json -o chart.svg
$ chartlet render broken.json -o chart.svg
chartlet: series_length_mismatch at /series/0/values: expected 2 values, one per category; use null for a missing value
$ echo $?
1
3.2 KB of SVG for three categorieserror → exit 1
As of September 2026 · v0.1.0-alpha.3
3
Chart types: bar, line, time
100
Categories per chart, upper bound
4
Series or layers
MIT
License
01 — Overview

A chart is a build artifact, not a runtime feature.

Showing a chart on a static site usually means loading a charting library into the browser – for data that has not changed since the build. chartlet moves that step forward: the specification becomes SVG or HTML while the site is built, and the result is a file like any other.

Documentation →
  1. 01Deterministic — the same specification produces the same bytes, reviewable in a diff and cacheable like any build artifact
  2. 02Built for accessibility — every chart carries a title and a description, and the HTML output adds the complete data as a table
  3. 03Honest about problems — invalid input is rejected with a code, a JSON Pointer and a way to fix it
  4. 04Two ways in — a Rust CLI and crate, plus an Astro integration that renders at build time
02 — Input and output

One specification, two output profiles.

monthly-revenue.jsonSpecification
{
  "schemaVersion": 1,
  "type": "bar",
  "title": "Monthly revenue",
  "description": "Revenue increased in February and remained above January in March.",
  "source": "Example data",
  "categoryAxis": { "title": "Month" },
  "valueAxis": { "title": "Revenue in thousands", "format": "number" },
  "data": [
    { "label": "January", "value": 120 },
    { "label": "February", "value": 180 },
    { "label": "March", "value": 150 }
  ]
}
chart.svgSVG · excerpt
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="450" viewBox="0 0 800 450"
  role="img" class="chartlet-root"
  aria-labelledby="chartlet-e92f7ad68bc8a2d6-title chartlet-e92f7ad68bc8a2d6-description">
  <title id="chartlet-e92f7ad68bc8a2d6-title">Monthly revenue</title>
  <desc id="chartlet-e92f7ad68bc8a2d6-description">Revenue increased in February
    and remained above January in March.</desc>
  <style>.chartlet-root{--chartlet-text:#172033;--chartlet-muted:#344054;
    --chartlet-grid:#d9dee8;--chartlet-accent:#2563eb;--chartlet-background:#fff; …}</style>
  …
</svg>
chart.htmlHTML · excerpt
<figure class="chartlet-figure">
  <figcaption>Monthly revenue</figcaption>
  <svg …>…</svg>
  <p class="chartlet-source">Source: Example data</p>
  <details class="chartlet-data">
    <summary>Show chart data</summary>
    <table>
      <caption>Data for Monthly revenue</caption>
      <thead><tr><th scope="col">Category</th><th scope="col">Value</th></tr></thead>
      <tbody>
        <tr><th scope="row">January</th><td>120</td></tr>
        <tr><th scope="row">February</th><td>180</td></tr>
        <tr><th scope="row">March</th><td>150</td></tr>
      </tbody>
    </table>
  </details>
</figure>
03 — Chart types

Bars, lines, time series – and nothing else.

Bars, vertical

"type": "bar" with data: a list of label and value. The default case, 800 × 450 pixels unless you say otherwise.

Bars, horizontal

"orientation": "horizontal" – negative values included; the zero line is then drawn in its own right.

Grouped bars

categories and series instead of data, up to four series. The legend is added automatically and the data table gets one column per series.

Lines with gaps

"type": "line" with null for an observation that does not exist. chartlet leaves a visible gap instead of drawing a line across it; the table shows “Missing”.

Time series

"type": "time" with panes and layers draws on a calendar axis. Timestamps as Unix seconds or ISO 8601, the timezone as a fixed UTC offset, UTC by default.

Limits in the contract

Up to 100 categories and four series; a time pane carries four layers of 2000 observations each. Labels must be unique. Anything beyond that is rejected rather than silently trimmed.

04 — Interaction

Controls without a line of chart JavaScript.

Series filter

A grouped chart gets a checkbox per series. Deselecting one hides its bars and value labels with CSS :has(); the axis does not rescale. Browsers without :has() simply keep every series visible.

Zoom steps

Two to four zoomSteps pre-compute narrower views of the same chart, switchable with radio buttons. Each step costs its own SVG in the file.

Tooltips

Every bar and point carries a native <title>. The alternative for assistive technology, though, is the description and the data table – not these hover-only hints.

05 — Get started

Install, write a specification, render.

  1. 1Install

    Get the CLI from crates.io

    Rust 1.88 or newer. While chartlet is in alpha, name the version explicitly.

    cargo install chartlet --version 0.1.0-alpha.3
  2. 2Render

    Compile the specification

    Without --format you get a standalone SVG; with --format html a <figure> including source and data table. - reads from standard input.

    chartlet render spec.json --format html -o chart.html
  3. 3Astro

    Render while the site builds

    In the alpha, @casoon/chartlet calls the CLI – you need both, and chartlet must be on PATH or reachable through CHARTLET_BIN.

    --- import Chart from '@casoon/chartlet/astro'; import revenue from '../data/monthly-revenue.json'; --- <Chart id="monthly-revenue" spec={revenue} />
  4. 4CI

    Turn warnings into errors

    --strict fails the run on any warning – for instance when a label had to be shortened or a value label left out.

    chartlet render spec.json --strict -o chart.svg
06 — Accessibility

What is in the chart – and what is still open.

chartlet is built for accessibility but has not been tested with screen readers yet. The points below are implemented in the source; testing with VoiceOver and NVDA is still pending.

  1. 01

    An image with a name and a description

    The SVG carries role="img" and points through aria-labelledby at <title> and <desc>. Without your own description, chartlet generates one from the data – for example: “Bar chart with 4 categories and 2 series (Budget, Actual). Highest: 160 (Budget in April). Lowest: 120 (Budget in January). 1 value is missing.”

  2. 02

    The data as a real table

    The HTML output always contains a <table> with a caption, th scope="col" and th scope="row" – including values whose visible label had no room. By default it sits in a native <details>; --table visible shows it permanently.

  3. 03

    Colours with contrast

    Series colours stay distinguishable for the common forms of colour-vision deficiency and keep at least 4.5:1 against white; in the dark theme the lowest series colour reaches 7.65:1. The legend lists series in the order of the bars.

  4. 04

    Nothing dropped silently

    Shortened labels and omitted value labels produce warnings (text_truncated, value_labels_omitted). The full text stays in the specification and in the data table.

07 — Support matrix

Verified, designed, planned.

ContextStatusNote
Standalone SVGVerifiedByte-identical on macOS and Linux, covered by golden-file tests
HTML figure with data tableVerifiedThe table is always present, in a native disclosure by default
Astro componentVerifiedRenders at build time and ships no chart JavaScript
Chromium (Chrome, Edge)Verifiedaxe scan in CI and manual review during development
`<img src="chart.svg">`DesignedTitle and description survive; figure and data table do not apply
Series filter and zoom stepsDesignedAvailable in the HTML profile; broader browser verification pending
Firefox and SafariPlannedStandard SVG is expected; the accessibility tree is untested
VoiceOver, NVDA, JAWSPlannedNot yet tested
Markdown, PDF and print, e-mailPlannedNot yet evaluated
WASM build and native Node bindingsNot supportedPlanned after the MVP; the alpha shells out to the CLI

The full matrix, themes and time series included, is in the documentation.

Development status · As of September 2026

Implemented

Bars, lines, time series

Bar charts single and grouped, vertical and horizontal, categorical line charts and time series on a calendar axis. Plus the HTML output with its data table, the series filter and zoom steps, the JSON schema as a contract, and golden-file tests for the output.

In progress

Early alpha, version 0.1.0-alpha.3

The first release has been on crates.io since 11 September 2026, with the npm package at the same version. The specification may still change before the first stable release – which is why schemaVersion is a required field from the start.

Planned

Marks, screen-reader testing, bindings

The mark types area, ohlc, band and annotation are currently refused with mark_not_implemented, several panes with too_many_panes. Also open: testing with VoiceOver and NVDA, forced-colours support, and a WASM build plus native Node bindings instead of the CLI call.

08 — Output

Three examples, straight from the repo.

Monthly revenue bar chart generated by chartlet
examples/monthly-revenue.svg — unmodified chartlet output.
Budget versus actual costs chart generated by chartlet
examples/budget-vs-actual.svg — unmodified chartlet output.
Revenue by channel chart generated by chartlet
examples/revenue-by-channel.svg — unmodified chartlet output.

The files come from examples/ in the repo, generated with chartlet 0.1.0-alpha.3. Each carries a title and description inside the SVG.

09 — Limits

When chartlet is the wrong tool.

chartlet publishes a finished chart. It is not a tool for exploring data.

Interactive analysis

There is no continuous zooming, panning, cross-filtering, live updating or keyboard-addressable detail for individual marks.

Data that changes at runtime

Rendering happens at build time. Values that move by the minute need something else.

Other kinds of chart

Maps, networks, 3D and anything beyond bars, lines and time series are out of scope.

Two installations

In the alpha, the npm package calls the Rust CLI. A build server therefore needs both.

Not yet tested with screen readers

The output is designed for accessibility, but testing with VoiceOver, NVDA and JAWS is still pending. Until then it counts as planned, not as verified.

10 — FAQ

Frequently asked questions.

What does “deterministic” mean in practice?

The same specification produces the same bytes. The repository’s golden-file tests check this for macOS and Linux. In practice it means a chart can be reviewed, diffed and cached like any other build artifact.

How does chartlet report an error in the specification?

With a code, the location as a JSON Pointer and a hint on how to fix it, for example: chartlet: series_length_mismatch at /series/0/values: expected 2 values, one per category; use null for a missing value. The process then exits with code 1.

What is the difference between an error and a warning?

An error stops the rendering. A warning goes to stderr and the chart is still produced. There are four warning codes: text_truncated, value_labels_omitted, dense_chart and color_not_supported. With --strict every warning becomes an error.

Do I need Rust to use chartlet in an Astro project?

In the alpha, yes. The npm package @casoon/chartlet calls the Rust CLI, so both must be installed and chartlet must be on PATH or reachable through CHARTLET_BIN. Native Node bindings are planned but not there yet.

What does the chart look like in dark mode?

With "theme": "dark", chartlet sets the same CSS custom properties to a dark palette and paints its own background. The lowest series colour reaches 7.65:1 contrast against #0e131c. There is no runtime switch – the theme is part of the specification.

Can I put the same chart on a page twice?

Yes, but with --id-prefix. The IDs for the title and the description are derived from the specification; without your own prefix they would appear twice, identically, in the document.

Charts that are finished before the browser.

chartlet is MIT-licensed and fully on GitHub – with a JSON schema, a set of examples and a gallery that puts every specification next to its output.