Framework integration
Keep data fetching in your application and mount Quartile inside a small interactive React boundary.
Package requirements
The preview exports ESM, TypeScript declarations, one stylesheet and a JSON Schema. Its React and React DOM peer range is >=18.2; packed consumers are tested on React 18.2 and 19.3. Install the built tarball using Installation; the package is not yet available on npm.
This documentation site runs the same tarball with Next.js and React. The public gallery uses Vite. No Tailwind installation or chart-specific provider is required beyond QuartileProvider.
Set provider locale and timeZone explicitly for reproducible server/client reports. Field and descriptor overrides, number precision, scientific notation and optional FormatJS/math.js callbacks are covered in Formatting and notation.
Studio exports a complete Vite starter with native components, a matching API helper and the built preview tarball. The API guide covers direct integration into an existing app, including CORS, request states, normalization and attribution.
The optional @quartile/react/ai entry provides reviewed assistance without a provider SDK. Dataframes use a snapshot handoff, with file or URL loading in the explorer; Quartile does not run Python.
Next.js App Router
Import the stylesheet in your root layout. Put hooks, event handlers and the provider in a module beginning with 'use client'. Pass serializable rows from a Server Component; define formatter functions on the client.
// app/layout.tsx
import '@quartile/react/styles.css';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <html lang="en"><body>{children}</body></html>;
}// app/reports/revenue-explorer.tsx
'use client';
import { useMemo } from 'react';
import { BarList, DataTable, FilterBar, KPI, Histogram,
QuartileProvider, Selection, dataset } from '@quartile/react';
export type Order = {
id: string; date: string; region: string; amount: number;
};
export function RevenueExplorer({ rows }: { rows: Order[] }) {
const orders = useMemo(() => dataset(rows, {
amount: { label: 'Revenue', format: 'currency', currency: 'USD' },
}), [rows]);
return (
<QuartileProvider theme="system">
<Selection>
<FilterBar data={orders} fields={[{ field: 'region' }]} />
<KPI data={orders} label="Revenue" value="amount" />
<Histogram data={orders} x="date" value="amount" interval="day" brush
valueFormat="currency-compact" aria-label="Revenue per day" />
<BarList data={orders} category="region" value="amount" select />
<DataTable data={orders} columns={[
{ field: 'date' }, { field: 'region' }, { field: 'amount' },
]} pageSize={10} />
</Selection>
</QuartileProvider>
);
}Keep authentication and database credentials in your server data layer. The example below uses an application-owned data function; Quartile does not ship this function or fetch rows.
// app/reports/page.tsx — Server Component
import { RevenueExplorer } from './revenue-explorer';
import { getAuthorizedOrders } from '@/data/orders';
export default async function ReportsPage() {
const rows = await getAuthorizedOrders();
return <RevenueExplorer rows={rows} />;
}See the Next.js server and client component guide for the boundary and serialization rules.
Vite and existing React apps
Import @quartile/react/styles.css once in the application entry. Render the same RevenueExplorer without the server/client directive. Use your existing data-fetching layer, and replace the rows array when a new response arrives.
import { createRoot } from 'react-dom/client';
import '@quartile/react/styles.css';
import { RevenueExplorer } from './revenue-explorer';
const rows = [
{ id: 'order-1', date: '2026-10-01', region: 'Europe', amount: 240 },
{ id: 'order-2', date: '2026-10-02', region: 'Americas', amount: 310 },
];
createRoot(document.getElementById('root')!).render(<RevenueExplorer rows={rows} />);Loading, errors and empty results
Keep a chart in place while data is loading. Send a useful error message and a retry callback on failure. An empty dataset should explain which filter or time window to change.
// daily has one aggregated row per date.
<LineChart
data={daily}
x="date"
y="amount"
loading={isPending}
error={error ? 'The report could not be loaded.' : undefined}
onRetry={refetch}
empty={{
title: 'No orders in this period',
description: 'Try a wider date range or clear the region filter.',
}}
aria-label="Daily revenue"
/>These variables come from your application's request state. Quartile displays them; it does not own retries or cache invalidation.
Alongside an existing design system
Use your existing shell, navigation and forms, then introduce Quartile for one report. Override --q-* tokens on the provider subtree. Load overrides after the library stylesheet, and verify overlays as well as charts in both themes.
Start with the token reference. Keep one mounted selection scope per linked report, and use selection={false} on a view that should stay independent.