Skip to content
quartiledocsv0.1
GitHub

Generating dashboards with AI

PreviewJSON SchemavalidateSpecSpecView

Give a model the JSON Schema, check what it returns with validateSpec, and render it with SpecView: the same components, with the same linked selection.

Complete dashboards and reviewed changes

This guide describes complete declarative dashboard layouts. For a model to propose bounded filters, sorting, chart choices or table fields in an existing workspace, use AI-assisted analysis. AnalysisPlan has its own schema and explicit review/apply lifecycle; it is not a Dashboard spec. Both require application-owned data authorization and model integration.

To compose visually without a model, use Studio. It connects real public API data and exports native React components. Its versioned editor project is a separate document from a Dashboard spec.

The schema

quartileSchema is a JSON Schema (draft 2020-12) with 28 definitions. 16 of them are components a spec can name: LineChart, AreaChart, BarChart, BarList, DonutChart, Funnel, Sparkline, Histogram, ScatterPlot, Heatmap, CalendarHeatmap, Sankey, BoxPlot, KPI, DataTable, FilterBar. The rest describe fields, measures, formats, table columns, a single spec and a Dashboard: a list of specs on a 12-column grid, linked by one selection.

It ships as the quartileSchema export and as @quartile/react/schema.json. You can also fetch the schema from this documentation site; the source is spec/schema.ts. SPEC_COMPONENTS lists the component names. Specs name datasets and fields as strings and formats with names, Intl number options or typed descriptors; callbacks remain React-only. Runtime format validation checks Intl option conflicts as well as structural rules.

Try it

Edit the spec. The status line is validateSpec's verdict; the dashboard underneath is SpecView rendering it with this site's sample orders. Load invalid to see the errors, or brush the line to see the selection link the generated views.

spec
✓ validateSpec: valid

Kestrel Goods · Sep 7 – Oct 6, 2026

RevenueUSD
$429.8K+4.3%
vs. previous period
Orders
3,295
Avg. order valueUSD
$130.45
Revenue per day
Drag to brush a date range

Revenue from Sep 7 to Oct 6: up 41.8%, from $11,354.82 to $16,105.14. Low $10,692.24 on Sep 12; high $17,634.15 on Oct 5.

Revenue per day
DateRevenue
Sep 7$11,354.82
Sep 8$12,840.26
Sep 9$14,508.45
Sep 10$13,346.86
Sep 11$13,140.61
Sep 12$10,692.24
Sep 13$12,220.77
Sep 14$13,383.08
Sep 15$13,387.31
Sep 16$14,098.99
Sep 17$14,155.77
Sep 18$16,846.30
Sep 19$13,498.44
Sep 20$11,783.19
Sep 21$16,571.15
Sep 22$16,690.85
Sep 23$17,405.41
Sep 24$14,769.58
Sep 25$16,867.29
Sep 26$13,911.34
Sep 27$12,332.15
Sep 28$14,639.16
Sep 29$14,796.44
Sep 30$16,899.36
Oct 1$13,455.41
Oct 2$15,227.22
Oct 3$14,661.34
Oct 4$12,618.84
Oct 5$17,634.15
Oct 6$16,105.14
Revenue by region

Revenue by region, 5 items. Highest North America at $179,327.88; lowest Middle East & Africa at $22,776.36.

Revenue by region
RegionRevenue
North America$179,327.88
Europe$118,152.67
Asia Pacific$74,519.75
Latin America$35,065.26
Middle East & Africa$22,776.36
Top products
Field Jacket$132.3K+19.0%
Trail Runner 2$55.5K+7.6%
Hiking Boot$42.1K+28.4%
Wool Throw$39.0K+13.7%
Stoneware Set$29.6K+0.2%

Prompting a model

Tell the model which datasets exist and which fields they have, and include the schema. Many model APIs can also take a JSON Schema for structured output; they support different subsets of the standard, so validate the result either way.

import schema from '@quartile/react/schema.json';

const system = [
  'Answer with one JSON object that validates against this JSON Schema.',
  'Datasets: "orders" (one row per order: id, date, region, channel, category, product, amount).',
  'Use only fields from those datasets.',
  'Order rows repeat dates. For a daily LineChart, encode y as { field: "amount", aggregate: "sum" }.',
  JSON.stringify(schema),
].join('\n\n');

Validate, then render

validateSpec reports unknown components, unknown properties, wrong types, out-of-range values and missing required fields. Each error has a JSON Pointer path and a sentence you can show a person or send back to the model.

import { SpecView, validateSpec } from '@quartile/react';

const spec = JSON.parse(modelOutput);
const result = validateSpec(spec);   // { valid, errors: [{ path: '/layout/3/curve', message }] }

// SpecView validates too: invalid items render an error card in place instead of throwing.
<SpecView spec={spec} data={{ orders, daily }} />

SpecView runs the same validation first. Invalid items, unknown components and missing datasets render an error card in their place, so one bad item does not take down the dashboard.

validateSpec checks the spec's shape, not your data. It does not know which fields a dataset has, so check field names against your data before you trust a generated spec. Authorize datasets in your application, limit input size and layout length before parsing untrusted output, and keep model credentials on the server.

Sending errors back

Each error names the path and what was expected, so the list can go straight back to the model with a request for a corrected spec. Cap the attempts.

async function generateSpec(question: string, attempts = 3) {
  let messages = [{ role: 'user', content: question }];
  for (let i = 0; i < attempts; i++) {
    const text = await callYourModel(system, messages);   // your model client
    let report: string;
    try {
      const spec: unknown = JSON.parse(text);
      const { valid, errors } = validateSpec(spec);
      if (valid) return spec;
      report = errors.map((e) => `${e.path || '/'}: ${e.message}`).join('\n');
    } catch {
      report = 'Return valid JSON without a Markdown code fence.';
    }
    messages = [
      ...messages,
      { role: 'assistant', content: text },
      { role: 'user', content: `That spec is not valid:\n${report}\nReturn a corrected spec.` },
    ];
  }
  throw new Error('No valid spec');
}
Report an issueBuilt Oct 21, 2026