Charts & widgets
Dashboards are assembled from the blocks in this group: cards that headline one value or a ranking, charts that plot series and distributions, and a period selector that drives one date range across every widget bound to it. Each block fetches its own data from a backend route; you arrange the result in a Grid.

Run the demo template and open Components → Dashboard to explore this screen with deterministic sample data.
The blocks
Compose a dashboard
Put one PeriodSelector at the top, then a Grid of GridRows holding the cards, all sharing its periodScope:
import { PageController, RegisterPage, pagesCategory } from "@antelopejs/interface-dms/page";
import { PeriodSelector } from "@antelopejs/interface-dms/base/period-selector";
import { Grid, GridRow } from "@antelopejs/interface-dms/base/grid";
import { KpiCard } from "@antelopejs/interface-dms/base/kpi-card";
import { ChartCard } from "@antelopejs/interface-dms/base/chart-card";
import { ChartArea } from "@antelopejs/interface-dms/base/chart";
import { TopListCard } from "@antelopejs/interface-dms/base/top-list-card";
@RegisterPage()
export class SalesPage extends PageController("sales", {
displayName: "$shop.sales.title",
icon: "i-ph-chart-line-up",
category: pagesCategory,
}) {
static period = PeriodSelector({ id: "sales", defaultPreset: "last-30-days" });
static dashboard = Grid()
.child(
"kpis",
GridRow()
.child("revenue", KpiCard({ title: "$shop.sales.revenue", fetchUrl: "/api/sales/revenue", periodScope: "sales", valueFormat: "currency", currencyCode: "EUR" }))
.child("orders", KpiCard({ title: "$shop.sales.orders", fetchUrl: "/api/sales/orders", periodScope: "sales" })),
)
.child(
"details",
GridRow()
.child("trend", ChartCard({ title: "$shop.sales.trend", fetchUrl: "/api/sales/series", periodScope: "sales", chart: ChartArea() }))
.child("products", TopListCard({ title: "$shop.sales.top_products", fetchUrl: "/api/sales/top-products", periodScope: "sales" })),
);
}
Shared options
The cards and the charts share the data options of every block (Block fundamentals): fetchUrl, fetchUrlMethod, periodScope and realtimeTopic. The three cards also share:
| Option | Type | Purpose |
|---|---|---|
valueFormat | "number" | "currency" | "percent" | "compact" | How values are written. |
currencyCode | string | ISO code used when valueFormat is currency. |
valuePrecision | number | "native" | Fixed fraction digits, or the Intl defaults. Code only. |
showDelta | boolean | The change-versus-comparison badge. |
Value precision
valuePrecision opts into fixed fraction digits (a non-negative integer Intl.NumberFormat supports) or "native" for the Intl defaults. Native currency formatting follows the currency's minor units: EUR uses two digits, JPY zero, KWD three. It changes display only, never the amounts.
Without it, currency rounds to whole units, percent and compact use at most one fraction digit, and number uses the Intl defaults. ChartCard passes the policy to its headline, its previous value and its chart's value axis and tooltip. Delta badges keep their own one-decimal policy.
ChartCard({
title: "Spend",
fetchUrl: "/api/usage/spend",
valueFormat: "currency",
currencyCode: "EUR",
valuePrecision: "native",
chart: ChartLine(),
});
12.5 reads +12.5 %, for a value going from 100 to 112.5. Never send the 0.125 ratio.