d/ dashctl
panel.hero type: kpisize: 8×4
open source · MIT licensed

Production-grade dashboards
in a single YAML file.

From dashboard.yaml to interactive HTML, in one file. Declarative, AI-friendly, ships as a self-contained artifact. No server, no SaaS, no tracker.

panel.metrics type: kpi
bundle (hello)
950kb
— gzipped, no DuckDB
panel types
17
runtime servers
0
— static HTML output
data sources
5
csv · parquet · http · duckdb · bq
showcase examples/sales/dashboard.yaml → 1 file
panel.showcase source: orders pages: 3 cross-filter: on open ↗
grammar declarative · flat · LLM-writable
dashboard.yaml 14 linesutf-8
- type: bar title: Revenue by category source: orders query: SELECT category, SUM(amount) AS revenue FROM orders GROUP BY 1 ORDER BY 2 DESC x: category y: revenue select: mode: multi on: category emit: - highlight: ["*"] # click to dim everything else
panel.grammar type: text

Flat, predictable, hand-writable.

Every panel is a row in a list. Every interaction is a declarative emit: edge. No JavaScript, no callbacks, no "now wire the click handler to the store dispatcher." The format is small enough to keep in your head and stable enough for an LLM to write end-to-end on the first try.

  • emit: filter — click writes a value into a named filter; everything that depends re-queries.
  • emit: highlight — visual dim only, no re-query; works across renderers and pages.
  • select: brush — drag a range across a histogram; binds to @filter.from / .to.
  • affects: / affected_by: — explicit wiring when the wildcard is too broad.
That highlighted emit: highlight: ["*"] is the whole cross-filter. Click a bar — every other panel on every page dims non-matching marks at 0.25 opacity. No SQL re-run.
why dashctl three things you can't buy
feature.crossfilter killer

Cross-filter & brush

Click anywhere in any chart — every other chart updates. Drag a date range. Multi-select with shift-click. Brush a histogram for a numeric range. URL-shareable: copy the link, the receiver sees the same view.

feature.renderers plot + echarts

Two renderers, one grammar

Plot for clean small-multiples and time series. ECharts for funnel, sankey, gauge, geo, treemap, heatmap. Lazy-loaded: hello dashboards ship at 950 KB; the full 28-panel showcase weighs 2.8 MB. Pay only for what you draw.

feature.ai_native llm-friendly

AI-native

Schema is intentionally flat and predictable so Claude (or any LLM) can author a working dashboard from a single prompt: "build me a sales dashboard from orders.csv" → valid YAML → one HTML file. No magic strings, no hidden state.

how it stacks up the dashctl-shaped hole
panel.compare type: tablerows: 5
tool output server? code or click? ai-friendly?
dashctl One HTML file No YAML (declarative) Yes — designed for it
Tableau Tableau Server / Cloud Yes (paid) Click (drag-and-drop) No (binary workbook)
Evidence.dev Static site (multi-file) No Markdown + SQL + Svelte Partial (mixed surfaces)
Observable Framework Static site (multi-file) No JS + Markdown Partial (JS-heavy)
Streamlit Python app Yes (Python runtime) Python (imperative) Partial (imperative state)
If you need a multi-tenant analytics platform, buy Tableau. If you need a single self-contained artifact your CI can email, that an agent can write, and that diffs in git — that's the dashctl-shaped hole.
get started three commands · 30 seconds
step.install 01
1 Install the CLI
npm install -g dashctl

One package, no peer-dep dance. Node 20+.

step.scaffold 02
2 Scaffold a dashboard
dashctl init my-dashboard

Working dashboard.yaml + a tiny CSV. Hand it to your coding agent as a one-shot reference for the format.

step.build 03
3 Build → one HTML
dashctl build dashboard.yaml -o out.html

Self-contained file. Drop on S3, attach to email, embed in a portal — it just runs.