Material UI and other design systems
The viewer and designer take every color, the corner radius and the font from CSS variables. The theme prop sets them, and themeFromMui builds that theme from a Material UI theme in one call. When your app switches between light and dark mode, the report UI follows.
Material UI
Section titled “Material UI”import { useMemo } from 'react';import { useTheme } from '@mui/material/styles';import { ReportDesigner, ReportViewer, themeFromMui } from '@ariana-reports/react';
export function ReportScreen({ template, data }) { const muiTheme = useTheme(); // Memoize: a new object on every render would re-apply the variables each time. const theme = useMemo(() => themeFromMui(muiTheme), [muiTheme]);
return ( <> <div style={{ height: 600 }}> <ReportViewer theme={theme} template={template} data={data} /> </div> <div style={{ height: 800 }}> <ReportDesigner theme={theme} template={template} sampleData={data} /> </div> </> );}themeFromMui reads the palette (primary.main, background, text, divider, error), palette.mode, shape.borderRadius and typography.fontFamily/fontSize. It accepts any object of that shape, so the packages have no dependency on Material UI. Use the second argument to override single values:
themeFromMui(muiTheme, { bandColor: muiTheme.palette.grey[800], radius: 8 });The result is a plain object. The pages and the design surface stay white like paper in every theme, because a report is a printed document.
Vuetify and other libraries
Section titled “Vuetify and other libraries”Build the theme object yourself from your design system’s tokens. Every property is optional:
import { ReportViewer, type ArianaTheme } from '@ariana-reports/react';
const theme: ArianaTheme = { mode: 'light', primary: 'var(--brand-500)', surface: 'var(--surface)', border: 'var(--border)', text: 'var(--text)', radius: 8, fontFamily: 'Inter, sans-serif',};
<ReportViewer theme={theme} template={template} data={data} />;<script setup lang="ts">import { computed } from 'vue';import { useTheme } from 'vuetify';import { ReportViewer, type ArianaTheme } from '@ariana-reports/vue';
const vuetify = useTheme();// Re-computed when the Vuetify theme switches between light and dark.const theme = computed<ArianaTheme>(() => { const c = vuetify.current.value.colors; return { mode: vuetify.current.value.dark ? 'dark' : 'light', primary: c.primary, onPrimary: c['on-primary'], background: c.background, surface: c.surface, text: c['on-surface'], danger: c.error, };});</script>
<template> <ReportViewer :theme="theme" :template="template" :data="data" /></template>Values can be any CSS color, including var(--your-token).
Try the themes
Section titled “Try the themes”import { useState } from 'react';import { DARK_THEME, ReportViewer, themeFromMui } from '@ariana-reports/react';import { salesData, salesTemplate } from './report-data';
// Any object shaped like a Material UI theme works, so in a MUI app pass useTheme() directly.const grape = themeFromMui({ palette: { mode: 'light', primary: { main: '#7b1fa2' }, background: { default: '#f3e5f5', paper: '#ffffff' } }, shape: { borderRadius: 14 },});
const themes = { default: undefined, dark: DARK_THEME, grape } as const;
export default function ViewerTheme() { const [name, setName] = useState<keyof typeof themes>('grape'); return ( <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> <label style={{ padding: 8, background: '#fff', font: '13px system-ui' }}> Theme{' '} <select value={name} onChange={(e) => setName(e.target.value as keyof typeof themes)}> {Object.keys(themes).map((t) => ( <option key={t}>{t}</option> ))} </select> </label> <div style={{ flex: 1, minHeight: 0 }}> <ReportViewer template={salesTemplate} data={salesData} theme={themes[name]} zoomMode="fitWidth" showIssues /> </div> </div> );}<script setup lang="ts">import { computed, ref } from 'vue';import { DARK_THEME, ReportViewer, themeFromMui } from '@ariana-reports/vue';import { salesData, salesTemplate } from './report-data';
// Any object shaped like a Material UI theme works.const grape = themeFromMui({ palette: { mode: 'light', primary: { main: '#7b1fa2' }, background: { default: '#f3e5f5', paper: '#ffffff' } }, shape: { borderRadius: 14 },});
const themes = { default: undefined, dark: DARK_THEME, grape } as const;const name = ref<keyof typeof themes>('grape');const theme = computed(() => themes[name.value]);</script>
<template> <div style="display: flex; flex-direction: column; height: 100%"> <label style="padding: 8px; background: #fff; font: 13px system-ui"> Theme <select v-model="name"> <option v-for="t in Object.keys(themes)" :key="t">{{ t }}</option> </select> </label> <div style="flex: 1; min-height: 0"> <ReportViewer :template="salesTemplate" :data="salesData" :theme="theme" zoom-mode="fitWidth" show-issues /> </div> </div></template>{ "schemaVersion": 1, "name": "Sales by customer", "unit": "mm", "page": { "size": "A4", "orientation": "portrait", "margins": { "top": 12, "right": 12, "bottom": 12, "left": 12 } }, "dataSources": [ { "name": "orders" } ], "styles": { "title": { "fontSize": 18, "fontWeight": "bold", "color": "#0f172a" }, "muted": { "fontSize": 9, "color": "#64748b" }, "th": { "fontSize": 9, "fontWeight": "bold", "color": "#ffffff", "backgroundColor": "#334155", "padding": 1.5 }, "td": { "fontSize": 9, "padding": 1.5, "border": { "width": 0.4, "color": "#e2e8f0", "style": "solid", "sides": [ "bottom" ] } }, "num": { "textAlign": "right" }, "sub": { "fontSize": 9, "fontWeight": "bold", "backgroundColor": "#e2e8f0", "padding": 1.5 } }, "reportHeader": { "id": "rh", "height": 16, "components": [ { "id": "title", "type": "text", "x": 0, "y": 0, "width": 186, "height": 9, "text": "Sales by customer", "styleName": "title" }, { "id": "sub", "type": "text", "x": 0, "y": 10, "width": 186, "height": 5, "text": "{{ count(orders) }} orders from {{ distinct(orders, \"customer\").length }} customers", "styleName": "muted" } ] }, "pageFooter": { "id": "pf", "height": 8, "components": [ { "id": "pf_n", "type": "text", "x": 0, "y": 2, "width": 186, "height": 5, "text": "Page {{ page.number }} of {{ page.count }}", "style": { "fontSize": 8, "color": "#64748b", "textAlign": "right" } } ] }, "body": [ { "kind": "dataRegion", "id": "orders_region", "dataSource": "orders", "sort": [ { "expression": "row.date", "direction": "asc" } ], "groups": [ { "id": "by_customer", "expression": "row.customer", "header": { "id": "gh", "height": 8, "components": [ { "id": "gh1", "type": "text", "x": 0, "y": 1, "width": 186, "height": 6, "text": "{{ group.key }}", "style": { "fontSize": 11, "fontWeight": "bold", "color": "#1d4ed8", "padding": 1 } } ] }, "footer": { "id": "gf", "height": 8, "components": [ { "id": "gf1", "type": "text", "x": 0, "y": 1, "width": 134, "height": 6, "text": "Subtotal ({{ group.count }} orders)", "styleName": "sub" }, { "id": "gf2", "type": "text", "x": 134, "y": 1, "width": 52, "height": 6, "text": "{{ sum(group.rows, \"amount\") }}", "styleName": "sub", "style": { "textAlign": "right" }, "format": { "type": "currency", "currency": "USD" } } ] } } ], "header": { "id": "head", "height": 7, "components": [ { "id": "h1", "type": "text", "x": 0, "y": 0, "width": 22, "height": 7, "text": "Order", "styleName": "th" }, { "id": "h2", "type": "text", "x": 22, "y": 0, "width": 30, "height": 7, "text": "Date", "styleName": "th" }, { "id": "h3", "type": "text", "x": 52, "y": 0, "width": 82, "height": 7, "text": "Product", "styleName": "th" }, { "id": "h4", "type": "text", "x": 134, "y": 0, "width": 52, "height": 7, "text": "Amount", "styleName": "th", "style": { "textAlign": "right" } } ] }, "detail": { "id": "row", "height": 6.5, "components": [ { "id": "c1", "type": "text", "x": 0, "y": 0, "width": 22, "height": 6.5, "text": "#{{ row.id }}", "styleName": "td" }, { "id": "c2", "type": "text", "x": 22, "y": 0, "width": 30, "height": 6.5, "text": "{{ row.date }}", "styleName": "td", "format": { "type": "date", "pattern": "dd MMM yyyy" } }, { "id": "c3", "type": "text", "x": 52, "y": 0, "width": 82, "height": 6.5, "text": "{{ row.product }}", "styleName": "td" }, { "id": "c4", "type": "text", "x": 134, "y": 0, "width": 52, "height": 6.5, "text": "{{ row.amount }}", "styleName": "td", "style": { "textAlign": "right" }, "format": { "type": "currency", "currency": "USD" } } ] }, "footer": { "id": "foot", "height": 10, "components": [ { "id": "f1", "type": "text", "x": 0, "y": 2, "width": 134, "height": 7, "text": "Grand total", "style": { "fontSize": 11, "fontWeight": "bold", "backgroundColor": "#1e293b", "color": "#fff", "padding": 1.5 } }, { "id": "f2", "type": "text", "x": 134, "y": 2, "width": 52, "height": 7, "text": "{{ sum(rows, \"amount\") }}", "style": { "fontSize": 11, "fontWeight": "bold", "backgroundColor": "#1e293b", "color": "#fff", "padding": 1.5, "textAlign": "right" }, "format": { "type": "currency", "currency": "USD" } } ] } } ]}{ "orders": [ { "id": 1001, "customer": "Contoso Ltd", "date": "2026-01-08", "product": "Cloud backup", "amount": 480 }, { "id": 1002, "customer": "Fabrikam", "date": "2026-01-11", "product": "USB-C dock", "amount": 129.9 }, { "id": 1003, "customer": "Contoso Ltd", "date": "2026-01-15", "product": "Support plan", "amount": 1200 }, { "id": 1004, "customer": "Northwind", "date": "2026-01-19", "product": "Monitor stand", "amount": 54.5 }, { "id": 1005, "customer": "Fabrikam", "date": "2026-02-02", "product": "Headset", "amount": 89 }, { "id": 1006, "customer": "Northwind", "date": "2026-02-05", "product": "Webcam HD", "amount": 75.25 } ]}Plain CSS
Section titled “Plain CSS”Without the prop, set the variables in a rule for .ar-root. The package’s defaults have zero specificity, so your rule wins whatever the stylesheet order:
.ar-root { --ar-primary: #6750a4; --ar-radius: 12px;}See Theming for the list of variables.