Build data apps,
not just charts.
Turn datasets into analytical workspaces. Explore editable tables, compare distributions and keep every view linked. Quartile brings 71 core React components, including 13 chart types, onto one data model — with optional, reviewable AI assistance.
Open-source preview. Start in Studio or follow the installation guide. Not published to npm yet.
- Version
- 0.1.0 preview · Oct 2026
- License
- Apache-2.0
- Runtime
- React 18 · 19
- Renderer
- SVG · Canvas / WebGL scatter
- Data in
- JSON rows · optional Arrow queries
- Core build
- 128 kB JS + 22 kB CSS gzipBefore tree-shaking; dependencies and worker assets excluded.
Loading the revenue workspace… Explore the storefront example
A live revenue workspace built with Quartile. Change the period, select a market, follow the data. Explore the full storefront example
Start with a question.
Build it your way.
Compose a workspace in React, or shape one visually in Studio. Start with a real API and keep the result in your own application.
Add Quartile to your app
Build and pack the preview, then import the components and stylesheet.
Connect an API
Use a documented fetch helper that returns typed rows, units and source metadata.
Compose your workspace
Bring charts, filters and tables together in React. Your application owns the data.
Weather forecast
One row per forecast hour. Explore temperature, humidity, precipitation and wind with their units attached.
Data from Open-Meteo. CC BY 4.0 data. The free endpoint is for non-commercial use.
The Studio requests data from the selected provider. You can inspect the source, retry a failed request and export the code. No account required.
Charts are a third of a data app.
Most teams glue a chart library to a UI kit, then spend weeks reconciling themes, tooltips and filter state. Quartile ships all three layers as one system, on the same tokens and the same data contract.
Forms, navigation, overlays and feedback for the rest of the app.
The analytical layer: editable explorers, metrics, filters, shared selection and the spec renderer.
One set of scales, axes and tooltips, from sparklines to sankeys.
Surfaces, series and states read the same CSS variables. Light and dark ship in one stylesheet; a brand theme is a remap of those variables.
Plain JSON rows in. Field types, labels and formats are inferred (or declared with dataset()) and shared by every chart, table and filter. An optional Arrow query source brings bounded worker results into the same model.
Brushes, clicks and filters publish to a shared store. Any component inside a <Selection> reads it; none of them need props for it.
Get closer to your data.
Inspect a distribution. Find the missing observations. Compare a subgroup, correct a value, and keep the view you made. An analytical table belongs in the same conversation as your charts.
Open the full exploration workspace- Mean yield
- 74.7%
- Missing yield
- 4
Search and filters shape the plot. A brush narrows the table. Means exclude missing values.
Yield distribution
Brush to inspect a rangeDistribution of Yield, 68 in 12 bins from 50.0% to 100.0%. Highest: 75.0% – 79.17% (8).
| Yield | Count |
|---|---|
| 50.0% – 54.17% | 3 |
| 54.17% – 58.33% | 6 |
| 58.33% – 62.5% | 7 |
| 62.5% – 66.67% | 5 |
| 66.67% – 70.83% | 7 |
| 70.83% – 75.0% | 6 |
| 75.0% – 79.17% | 8 |
| 79.17% – 83.33% | 6 |
| 83.33% – 87.5% | 6 |
| 87.5% – 91.67% | 5 |
| 91.67% – 95.83% | 7 |
| 95.83% – 100.0% | 2 |
Shape the view
Typed filters, search and sort priorities. Group records with explicit aggregates; pin, resize, reorder and hide columns.
Keep the reasoning
View state is serializable JSON. Save and restore a configuration, export all matching rows, and validate edits before your app accepts them.
Bring the next dataset
Pass typed JSON rows from pandas or Polars. The full workspace also imports CSV, JSON and dataframe snapshots in the browser.
Dataframe handoff guideFictional materials data, running entirely in this page. Edits last until reload; use the View controls to copy or load a view configuration. Explore the table API
Brush one view. Every view listens.
Wrap components in a Selection. Brushing, clicking and filtering publish to one shared state, with no prop drilling and no event buses. Try it below; the log on the right shows what was published.
Orders per day of Date, Sep 7 to Oct 6: total 3,295. Highest: Sep 22 (129).
| Date | Orders |
|---|---|
| Sep 7 | 94 |
| Sep 8 | 108 |
| Sep 9 | 103 |
| Sep 10 | 98 |
| Sep 11 | 106 |
| Sep 12 | 81 |
| Sep 13 | 88 |
| Sep 14 | 107 |
| Sep 15 | 111 |
| Sep 16 | 108 |
| Sep 17 | 104 |
| Sep 18 | 122 |
| Sep 19 | 103 |
| Sep 20 | 102 |
| Sep 21 | 116 |
| Sep 22 | 129 |
| Sep 23 | 128 |
| Sep 24 | 110 |
| Sep 25 | 111 |
| Sep 26 | 102 |
| Sep 27 | 97 |
| Sep 28 | 111 |
| Sep 29 | 119 |
| Sep 30 | 128 |
| Oct 1 | 113 |
| Oct 2 | 128 |
| Oct 3 | 108 |
| Oct 4 | 105 |
| Oct 5 | 127 |
| Oct 6 | 128 |
Amount by category, 5 items. Highest Apparel at $175,665.48; lowest Kids at $27,896.04.
| Category | Amount |
|---|---|
| Apparel | $175,665.48 |
| Footwear | $126,872.14 |
| Home | $68,646.86 |
| Accessories | $30,761.40 |
| Kids | $27,896.04 |
import { BarList, DataTable, Histogram, KPI, Selection } from '@quartile/react';
const kpis = [
{ label: 'Orders', aggregate: 'count' },
{ label: 'Revenue', value: 'amount', format: 'currency-compact' },
{ label: 'Avg. order', value: 'amount', aggregate: 'mean' },
];
const columns = [
{ field: 'id', label: 'Order' },
{ field: 'date' },
{ field: 'category' },
{ field: 'amount', cell: 'number' },
];
export function OrdersExplorer({ orders, onChange }) {
return (
<Selection id="orders" onChange={onChange}>
<KPI.Group data={orders} items={kpis} variant="strip" />
<Histogram data={orders} x="date" brush />
<BarList data={orders} category="category" value="amount" select />
<DataTable data={orders} columns={columns} sort="-amount" limit={5} />
</Selection>
);
}- readyselection "orders" · 3,295 rowsBrush or click a view to inspect its predicate here.
A proposal you can inspect.
Give an assistant the context you choose. Quartile validates its proposed filters, sorts and chart changes against your fields. You review the plan, then apply it to application-owned state.
Explore the assistant workspaceChoose a proposal to review.
Two deterministic recipes exercise the real assistant API. The brief delay makes cancellation visible. Your current view stays in place until you apply.
Inspect the context available to this adapter
Field metadata, bounded profile summaries and current filters. No raw records. Inspect these values before sending context to your own backend.
{
"version": 1,
"source": {
"id": "landing-materials",
"version": "1",
"label": "Simulated materials experiments"
},
"fields": [
{
"name": "id",
"label": "Run",
"type": "nominal",
"format": "text"
},
{
"name": "material",
"label": "Material",
"type": "nominal",
"format": "text"
},
{
"name": "batch",
"label": "Batch",
"type": "nominal",
"format": "text"
},
{
"name": "temperature",
"label": "Temperature (°C)",
"type": "quantitative",
"format": "integer",
"unit": "COUNT"
},
{
"name": "yield",
"label": "Yield",
"type": "quantitative",
"format": "percent"
},
{
"name": "reviewed",
"label": "Reviewed",
"type": "boolean",
"format": "text"
}
],
"selection": [],
"profile": {
"totalRows": 72,
"scannedRows": 72,
"complete": true,
"fields": [
{
"field": "id",
"type": "nominal",
"valid": 72,
"invalid": 0,
"missing": 0,
"distinct": 72
},
{
"field": "material",
"type": "nominal",
"valid": 72,
"invalid": 0,
"missing": 0,
"distinct": 3
},
{
"field": "batch",
"type": "nominal",
"valid": 72,
"invalid": 0,
"missing": 0,
"distinct": 6
},
{
"field": "temperature",
"type": "quantitative",
"valid": 72,
"invalid": 0,
"missing": 0,
"distinct": 72,
"min": 180,
"max": 300,
"mean": 240.6805555555556
},
{
"field": "yield",
"type": "quantitative",
"valid": 68,
"invalid": 0,
"missing": 4,
"distinct": 43,
"min": 0.53,
"max": 0.96,
"mean": 0.7467647058823532
},
{
"field": "reviewed",
"type": "boolean",
"valid": 72,
"invalid": 0,
"missing": 0,
"distinct": 2
}
]
},
"provenance": [
{
"label": "Source",
"detail": "72 deterministic, fictional observations generated in this page."
}
]
}Applied view
72 / 72 observationsAll 72 observations. No proposal applied.
Distribution of Yield, 68 in 10 bins from 50.0% to 100.0%. Highest: 80.0% – 85.0% (9).
| Yield | Count |
|---|---|
| 50.0% – 55.0% | 3 |
| 55.0% – 60.0% | 8 |
| 60.0% – 65.0% | 7 |
| 65.0% – 70.0% | 8 |
| 70.0% – 75.0% | 8 |
| 75.0% – 80.0% | 8 |
| 80.0% – 85.0% | 9 |
| 85.0% – 90.0% | 6 |
| 90.0% – 95.0% | 7 |
| 95.0% – 100.0% | 4 |
Your model. Your server. Your decision.
The optional @quartile/react/ai entry supplies the context, validation and request lifecycle. The runnable Node backend example keeps credentials on the server and supports a configured model. This page uses only the local adapter above.
Inspect the behavior behind the interface.
Time a selection on your device, navigate a chart with the keyboard, and edit a validated JSON spec. These demos run the same preview package you can build from source.
Open the worker query workbench250K rows, timed in your browser.
Pick a size, brush the histogram or click a region. The timer includes row generation on size changes and stops at the DOM commit, before paint. Rows become 40 SVG bins and four HTML region totals; this measures in-memory aggregation rather than raw-point rendering. Changing the source size retains your active selection; use Clear selection to reset it.
Explore Arrow queries and scatter renderers ↗Charts you can tab through.
12 bars. Highest Jun 26 at $1.01M; lowest Dec 25 at $566.4K.
| Month | Revenue |
|---|---|
| Nov 25 | $573.7K |
| Dec 25 | $566.4K |
| Jan 26 | $605.0K |
| Feb 26 | $648.5K |
| Mar 26 | $799.3K |
| Apr 26 | $876.1K |
| May 26 | $953.0K |
| Jun 26 | $1.01M |
| Jul 26 | $993.8K |
| Aug 26 | $928.8K |
| Sep 26 | $852.6K |
| Oct 26 | $815.7K |
Tab to the chart, then use ← → Home End. The dark panel mirrors the chart's own live region, word for word. Charts also carry an auto-written summary and a data-table fallback; switch to Table to see it.
Typed schemas, generated dashboards.
Starter from Jul 6 to Sep 28: up 26.5%, from 34 to 43. Low 34 on Jul 6; high 48 on Sep 7. Team from Jul 6 to Sep 28: up 30.8%, from 26 to 34. Low 21 on Jul 20; high 34 on Sep 28. Enterprise from Jul 6 to Sep 28: up 162.5%, from 8 to 21. Low 8 on Jul 6; high 21 on Sep 21. Total from Jul 6 to Sep 28: up 44.1%, from 68 to 98. Low 68 on Jul 6; high 98 on Sep 28.
| Week | Starter | Team | Enterprise | Total |
|---|---|---|---|---|
| Jul 6 | 34 | 26 | 8 | 68 |
| Jul 13 | 38 | 24 | 8 | 70 |
| Jul 20 | 40 | 21 | 10 | 71 |
| Jul 27 | 39 | 21 | 14 | 74 |
| Aug 3 | 37 | 23 | 17 | 77 |
| Aug 10 | 36 | 27 | 17 | 80 |
| Aug 17 | 38 | 31 | 14 | 83 |
| Aug 24 | 42 | 32 | 12 | 86 |
| Aug 31 | 46 | 30 | 11 | 87 |
| Sep 7 | 48 | 28 | 14 | 90 |
| Sep 14 | 47 | 27 | 18 | 92 |
| Sep 21 | 44 | 29 | 21 | 94 |
| Sep 28 | 43 | 34 | 21 | 98 |
This local demo registers signups: 39 fictional weekly plan totals, with week, plan and count fields. No model is called.
16 chart and data components are described by one JSON Schema. Hand it to a model, check what comes back with validateSpec, render it with SpecView.
Everything around the chart.
Inputs, overlays, navigation and feedback, designed for dense screens. Same tokens, same keyboard model, same density scale.
Browse the component library →One isolated sample: controls below filter the order summary and its CSV, without changing the other demos.
Amount by category, 5 items. Highest Footwear at $65.7K; lowest Kids at $25.3K.
| Category | Amount |
|---|---|
| Footwear | $65.7K |
| Home | $62.3K |
| Apparel | $61.0K |
| Accessories | $28.3K |
| Kids | $25.3K |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
Amount by region, 2 items. Highest North America at $179.3K; lowest Europe at $118.2K.
| Region | Amount |
|---|---|
| North America | $179.3K |
| Europe | $118.2K |
From first look to model review.
Start with a scientific question and a working interface. These examples use the public library and deterministic sample data; the explorer also accepts your own local files.
Read the data-science guideMeet a dataset for the first time.
Import a local CSV or JSON file. Inspect field profiles and missing values, filter the records, and follow their distributions through a linked table and charts.Profiles · Typed filters · Editable tablesOpen the dataset explorerCohort comparisonSee who returns, and when.
Compare retention by signup cohort, trace the eligible population at each interval, and inspect the members behind a selected group.Retention matrix · Observed intervals · MembershipCompare retention cohortsModel evaluationMake the tradeoffs visible.
Move a classification threshold and inspect the confusion matrix, precision, recall and prediction records together. Compare outcomes within a selected segment.Thresholds · Precision and recall · Confusion matricesInspect the model evaluationAssisted analysisReview the next step before taking it.
Inspect the context, request an analysis plan and review validated changes before applying them. Try the labeled local planner or connect your own backend adapter.Explicit context · Validated plans · Review and applyTry the assistant workspaceWorker query workbenchExplore more rows. Return fewer.
Keep Arrow records in a DuckDB browser worker. Link service totals, metrics and paged records, then compare scatter renderers using bounded points and measured updates.Arrow · DuckDB · Bounded resultsExplore worker queriesA clear path into your codebase.
Start with one linked view. Keep your data fetching, application logic and deployment choices. Quartile gives the interface a shared vocabulary.
Start buildingCan I start with a dataframe?
Yes. Export typed JSON rows from pandas or Polars, or use the snapshot helper and executable notebook in the repository. The explorer imports CSV, JSON and typed snapshots in your browser. This is a file handoff; it does not connect to or synchronize a running Python kernel.
Who owns edits and saved table views?
Your application does. DataExplorer exposes controlled view state for filters, sort priorities, grouping, column layout and pagination. Cell edits pass through validation and an onCellEdit callback; grouped values are read-only. Save the serializable view wherever you choose. It contains configuration, not a copy of the dataset.
Does the assistant call a model or change data automatically?
This landing demo uses two deterministic local recipes and makes no model request. The optional AI entry can call an adapter connected to your backend. It shares explicitly chosen metadata, summaries and active filters, validates a proposed plan, and waits for Apply. It changes application-owned analysis state; the example does not execute arbitrary code or edit source records.
How do I try Quartile today?
Open Studio to connect a public API and build a view visually, then download a React starter. For an existing app, clone the public repository and build and pack @quartile/react locally. The 0.1 preview is not published to npm, and APIs can still change.
Can I use my own API with Studio?
Yes, for a public JSON endpoint that allows browser requests. Studio maps a bounded array of flat records to charts and tables, then exports configuration and React code. For authenticated or private data, use your own backend and compose Quartile in your app. Studio does not host a backend or store API credentials.
Will it work with my existing React app?
Quartile targets React 18 and 19. Import its stylesheet, add the provider, and compose the parts you need. Use a client component boundary in Next.js; the framework guide also covers Vite.
Can it look like our product?
Charts, tables and controls read the same CSS custom properties. Start with the built-in light or dark theme, choose a density, then remap the colors, spacing and typography. No Tailwind or CSS-in-JS runtime is required.
Which renderer should I use?
Scatter plots default to SVG and also offer Canvas and WebGL, while keeping keyboard navigation, point selection and a paged table view. WebGL falls back to Canvas and then SVG when needed. Compare the active renderer on your own device; a different rendering path is not a speed guarantee.
Can I query larger datasets in a worker?
The optional Arrow and DuckDB adapter applies selections before aggregation and returns bounded results. The worker example loads up to one million generated source rows, pages raw records and plots at most 10,000 points. It reports source rows, returned rows, active renderer and timings separately; those sample sizes are not a universal capacity limit.
What should I check before shipping?
The optional worker query path needs separately installed Arrow and DuckDB peers and hosted assets. Profile your own data and devices. Keyboard navigation, summaries and table views are implemented; screen-reader validation and a full accessibility audit remain open work.
Free, Apache-2.0 licensed, built in public.
The code, the issues and the plans live on GitHub. 0.1 is a preview: APIs can still change before the first npm release, and bug reports shape what comes next.
- Core components
- 71
- Chart types
- 13
- Unit tests, passing
- 467
- License
- Apache-2.0
Counted from the 0.1.0 source on 2026-10-10: core-entry component exports and the Vitest suite. Optional query and AI helpers are outside this component count.