Your first designer
The designer edits a template visually. Give it a starting template (or none for a blank report), some sample data so it can list the fields, and a function that saves the result.
import { ReportDesigner } from '@ariana-reports/react';import template from './sales.template.json';import data from './sales.data.json';
export function SalesDesigner() { return ( <div style={{ height: '100vh' }}> <ReportDesigner template={template} sampleData={data} onSave={async (_template, json) => { await fetch('/api/reports/sales', { method: 'PUT', body: json }); }} /> </div> );}<script setup lang="ts">import { ReportDesigner } from '@ariana-reports/vue';import template from './sales.template.json';import data from './sales.data.json';
async function save(_template: unknown, json: string) { await fetch('/api/reports/sales', { method: 'PUT', body: json });}</script>
<template> <div style="height: 100vh"> <ReportDesigner :template="template" :sample-data="data" :on-save="save" /> </div></template>Try it
Section titled “Try it”Drag a Text from the left panel onto a band, drag a field from the Data tree to bind it, and press Preview to see the report with the sample data.
import { useState } from 'react';import { ReportDesigner, type ReportTemplate } from '@ariana-reports/react';import { salesData, salesTemplate } from './report-data';
export default function DesignerBasic() { const [template, setTemplate] = useState<ReportTemplate>(salesTemplate); return ( <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> <div style={{ flex: 1, minHeight: 0 }}> {/* The template prop is only the starting point; changes are reported through onChange. */} <ReportDesigner template={salesTemplate} sampleData={salesData} onChange={setTemplate} onSave={async (_template, json) => { // Send `json` to your backend here. console.log('save', json.length, 'bytes'); }} /> </div> <div style={{ padding: '4px 10px', background: '#fff', font: '12px system-ui', color: '#555' }}> Template JSON: {JSON.stringify(template).length} characters </div> </div> );}<script setup lang="ts">import { ref } from 'vue';import { ReportDesigner, type ReportTemplate } from '@ariana-reports/vue';import { salesData, salesTemplate } from './report-data';
const template = ref<ReportTemplate>(salesTemplate);
async function save(_template: ReportTemplate, json: string) { // Send `json` to your backend here. console.log('save', json.length, 'bytes');}</script>
<template> <div style="display: flex; flex-direction: column; height: 100%"> <div style="flex: 1; min-height: 0"> <!-- The template prop is only the starting point; changes are reported through the change event. --> <ReportDesigner :template="salesTemplate" :sample-data="salesData" :on-save="save" @change="template = $event" /> </div> <div style="padding: 4px 10px; background: #fff; font: 12px system-ui; color: #555"> Template JSON: {{ JSON.stringify(template).length }} characters </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 } ]}What you can do in the designer
Section titled “What you can do in the designer”- Add components (text, image, line, rectangle) by dragging them from the toolbox, or by clicking one to add it to the selected band. Drag data fields from the tree onto a band to create a bound text.
- Move and resize with the mouse. Handles sit on the edges of every selected component, so you can resize a whole row at once. Snapping to the grid and to other components helps with alignment, and Alt disables it while dragging.
- Move components between bands by dragging them over another band.
- Select several by dragging a rubber band, also from the empty strip below a band or from the page margin.
- Edit text in the properties panel, or double-click a text to open a large editor with formatting buttons and a searchable list of fields and functions.
- Add bands: headers and footers, data bands, groups and master-detail, from the Report properties.
- Style with fonts, colors, borders and conditions, and save styles by name.
- Undo and redo, copy and paste, and keyboard shortcuts.
- Preview the real report with your sample data.
Read the designer guide for the full list of props, shortcuts and the controller API.
Saving and loading
Section titled “Saving and loading”The designer does not own your storage. It calls onSave(template, json) when the user clicks Save (and downloads a .json file when you give no handler). To load a template, pass it as the template prop when the designer mounts. See Saving and loading templates.