ibcs-react is a component library for IBCS / ISO 24896 business reporting - statement tables, variance charts, KPI cards and whole dashboards that encode the standard so reports come out consistent, comparable and decision-ready.
One data model, many views. The same statement feeds the table, the charts and the KPIs. Components never fetch - bring your data (a report engine, an API, static JSON) and they render a normalized model. Sign-explicit variances, four scenario fills (actual / previous / plan / forecast), shared scales, and a built-in
checkIbcsconformance linter - among the first React libraries built around the IBCS / ISO 24896 notation.
The rolling forecast lands the year on plan: monthly AC vs PL columns, a variance bridge of the deviations, and the AC + FC year-end total - one chart, the whole story.
npm i ibcs-reactreact and react-dom (>=18) are peer dependencies - they are not bundled.
Try it without installing: there's a runnable Next.js (App Router) starter in
examples/nextjs -
Open in StackBlitz ↗
(works once ibcs-react is published to npm).
Building with a coding agent? Install the ibcs-react skills - IBCS notation
rules, component recipes, and ibcs-report, which turns a financial workbook
(a P&L, a balance sheet, a whole management pack) into a print-ready A4
board-report PDF drawn with ibcs-react components - packaged in the open
SKILL.md format for Claude Code, Cursor, Codex and 70+ other agents:
npx skills add NibelungAI/ibcs-react # pick from all three
npx skills add NibelungAI/ibcs-react@ibcs-report # statements → board pack PDFNo install needed either - the skills are served raw from the docs site (overview), so telling an agent to “read ibcs.at/skills/ibcs-report/SKILL.md and follow it” is enough.
Agents can also read the docs directly: ibcs-react.com/llms.txt
(index), llms-full.txt (whole corpus, one file),
or any docs page as raw Markdown by appending .mdx to its URL - e.g.
/docs/getting-started.mdx.
import { KpiCard, StatementTable, Report } from "ibcs-react";
import type { StatementLine } from "ibcs-react";
// ONE data model - values keyed by scenario (AC actual / PY previous year /
// PL plan / FC forecast) - feeds every component.
const statement: StatementLine[] = [
{ id: "rev-product", label: "Product revenue", flow: "add", values: { AC: 17.2e6, PY: 16.1e6 } },
{ id: "rev-service", label: "Service revenue", flow: "add", values: { AC: 12.9e6, PY: 9.5e6 } },
{ id: "revenue", label: "Revenue", flow: "result", values: { AC: 30.1e6, PY: 25.6e6 } },
{
id: "cogs",
label: "Cost of goods",
flow: "subtract",
higherIsBetter: false,
values: { AC: 9.7e6, PY: 8.4e6 },
},
{ id: "gm", label: "Gross margin", flow: "result", values: { AC: 20.4e6, PY: 17.2e6 } },
];
export function Dashboard() {
return (
<>
<KpiCard
label="Revenue"
values={{ AC: 30.1e6, PY: 25.6e6 }}
comparisons={["PY"]}
format={{ compact: true, decimals: 1 }}
/>
<StatementTable
lines={statement}
varianceColumns={[
// right-hand variance panels, in order
{ base: "PY", mode: "abs", mark: "bar" }, // ΔPY as bars
{ base: "PY", mode: "pct", mark: "pin" }, // ΔPY% as pins
]} // ← this pair is also the default
format={{ compact: true, decimals: 1 }}
/>
</>
);
}Prefer to drive a whole page from data? Hand a ReportConfig to <Report />:
<Report
config={{
title: { who: "ACME Group", what: "Revenue & margin", when: "FY 2026" },
columns: 12,
blocks: [
{
id: "k1",
type: "kpi",
span: 4,
config: { label: "Revenue", values: { AC: 30.1e6, PY: 25.6e6 }, comparisons: ["PY"] },
},
{
id: "k2",
type: "kpi",
span: 4,
config: { label: "Gross margin", values: { AC: 20.4e6, PY: 17.2e6 }, comparisons: ["PY"] },
},
{ id: "s1", type: "statement", span: 12, config: { lines: statement } },
],
}}
/>Everything is a tree of StatementLines carrying one value per scenario
(AC actual, PY previous year, PL plan/budget, FC forecast):
flow:"add"/"subtract"move the actual-column waterfall;"result"draws a full subtotal bar without moving the running total.higherIsBetter: setfalseon cost / expense / tax lines so an increase reads as unfavorable (red), even though the number is positive.children: any line can be a collapsible group whose children carry the flow.
All components are pure React + inline SVG. The library covers the complete IBCS template set - every standard chart (C01-C13) and table (T01-T04):
| Group | IBCS template | Component(s) |
|---|---|---|
| Tables | T01 hierarchical rows · variance columns | DataTable, ComparisonTable |
| T02 hierarchical rows · integrated bar charts | ComparisonTable |
|
| T03 / T04 measure rows · integrated waterfalls | StatementTable |
|
| Budget / control matrix | MatrixTable |
|
| Charts | C01 / C02 stacked column / stacked bar charts | StackedChart |
| C03 / C04 multi-tier column / bar charts | GroupedVarianceChart |
|
| C05 horizontal waterfall chart + columns | ColumnVarianceWaterfallChart, HorizontalWaterfallChart |
|
| C06 vertical waterfall chart + bars | BarVarianceWaterfallChart |
|
| C07 line · C08 area | LineChart · AreaChart, VarianceAreaChart |
|
| C09 scattergram · C10 bubble chart | ScatterChart · BubbleChart |
|
| C11 tree charts (calculation / ratio) | TreeChart, RatioTreeChart |
|
| C12 vertical waterfalls + variance | WaterfallChart, WaterfallStatementChart |
|
| C13 small multiples | SmallMultiples, MiniVarianceMultiples |
|
| (extras) integrated / ranking variance | IntegratedVarianceChart, RankingVarianceChart |
StatementTable- the IBCS integrated waterfall (T03/T04): a P&L (flow) or balance sheet (mode="stock") with embedded variance bars/pins, expand/ collapse-all, optional virtualization (maxHeight) for large consolidations.DataTable- the general cross-entity comparison table (T01): rows = entities, columns = measures (values, embedded variance bars/pins, sparklines).ComparisonTable- the centre-label flanking layout (T01/T02): one measure with column groups (e.g. month vs YTD), each PY/PL/AC + variance.MatrixTable- a budget / control matrix: a P&L row tree crossed with an expanding period column tree (Year → Quarter → Month), PL/AC/FC sub-columns, ΔBudget, sticky first column + horizontal scroll, expand-all periods.
StatementTable, DataTable and MatrixTable follow the React
controlled/uncontrolled convention (ComparisonTable is a static layout with
no interactive state). Seed them and
let them own their state - defaultCollapsed, defaultSort,
defaultExpandedRows / defaultExpandedCols - or take the state over with
collapsed + onCollapsedChange, sort + onSortChange, expandedRows /
expandedCols + onExpandedRowsChange / onExpandedColsChange, for URL sync,
persistence or two views kept in step. The on…Change callbacks also fire in
uncontrolled mode, as observers.
VarianceColumnChart- AC vs a comparison as overlapped columns (or pins) with an absolute/relative variance panel beneath.TrendChart- a many-period time series (built for 13): AC solid / FC hatched columns, PY & PL reference lines, variance panel.StructureChart- ranked horizontal bars for composition / contribution, AC vs PY overlap and % share.WaterfallChart- the IBCS vertical waterfall chart (C12): a standalone add / subtract / result bridge.StackedChart- the IBCS stacked column chart (C01, over time) or stacked bar chart (C02, over a structure), with category totals.LineChart- dense multi-series time lines with markers (C07).AreaChart- one scenario filled to the zero baseline (C08), optional reference line on top.ScatterChart- a value/value scattergram (C09) with optional constant-product iso-lines (e.g. equal gross profit).BubbleChart- two value axes plus a size dimension (C10).ComboChart- columns + a secondary-axis line.TreeChart/RatioTreeChart- the IBCS tree chart (C11), a calculation / DuPont tree: TreeChart shows a value per node, RatioTreeChart a mini time-series per node.GroupedVarianceChart- the IBCS multi-tier column chart (C03) / multi-tier bar chart (C04): grouped two-scenario columns or bars with stacked absolute + relative variance tiers.IntegratedVarianceChart- the signature vertical 3-tier chart: Δ% pins / Δ bars / AC columns with a PY-solid or PL-frame reference + FY total.RankingVarianceChart- a sorted horizontal AC + plan-overlay chart with ΔPL bar and ΔPL% pin columns and a total row.HorizontalWaterfallChart- the IBCS horizontal waterfall chart (C05); plus the compositesColumnVarianceWaterfallChart(C05),BarVarianceWaterfallChart(C06) andWaterfallStatementChart(C12, two side-by-side waterfalls + variance tiers).VarianceAreaChart- actual vs a reference with green/red gap fill and a hatched forecast tail.PieChart- a part-to-whole pie / donut and pie multiples. IBCS discourages pies;checkIbcsflags them - included for the occasional share.SmallMultiples/MiniVarianceMultiples- a grid of the same chart repeated by a dimension (C13), with an opt-in shared scale (the IBCS CHECK rule).VarianceBar- the standalone variance bar/pin primitive used inside the tables and charts.
KpiCard- a headline figure (count-up animated) with impact-coloured deltas vs PY/PL and an optional sparkline; anappearanceprop controls border / background / corner radius / accent bar / shadow. The unit comes fromformat:currencyfor a leading symbol (€30.1M),suffixfor a trailing one (18.4%), stated once beside the headline.Sparkline- a tiny line / area / bar micro-chart for cards and cells.
ChartBox/ResponsiveChart/ScrollChart- give a fixed-size chart its size: fit, alignment, padding and scroll (see Sizing). (ChartFrameis still exported, now deprecated in favour ofChartBox.)Skeleton- a loading placeholder shaped like a chart / table / card, drawn as one self-animating SVG.ChartState- loading / error / empty / content in a single wrapper, made to pair withuseAsyncData({ data, loading, error, refetch }).ChartDataTable- a visually-hidden<table>of the numbers behind a chart, so screen readers get the values and not just a label.
Report- renders a whole report from a JSONReportConfig: a responsive grid of KPI / chart / statement / text / table blocks.ConfiguredChart- renders any chart from a singleChartConfigobject (the discriminatedtypeselects the component).
checkIbcs- lint aChartConfig,KpiConfigorReportConfigagainst the ISO 24896 / IBCS rules; returns findings (error / warning / info).ConformanceReport- a React view that renders those findings.
useStatement, useVariance, useVariances, useFilters, useLiveData,
useAsyncData (API data with loading / refresh / abort / poll),
useChartSelection (click-to-filter), useChartHover (+ ChartTooltip),
usePrefersReducedMotion, useMountGrow, useAnimatedValue, useCountUp.
downloadCSV, downloadTextFile, downloadSVG, downloadPNG, serializeSvg
(browser-side export); ExportMenu (a ready SVG / PNG / CSV download menu).
Every chart takes an explicit pixel width / height - nothing measures itself
-
so a render-prop wrapper resolves the size and the chart re-renders at it. Unlike scaling a bitmap, text and strokes stay crisp at any size.
-
ChartBox- the one to reach for, and the primitive the others are presets of.fit="scale"(default) fills the available width at the chart's aspect ratio and stops shrinking atminWidth(scrolling past it);"fixed"always draws the intrinsic size and scrolls;"contain"scales to fit both dimensions and letterboxes the rest;"fill"stretches to the box. Addalign/verticalAlign,padding,backgroundandscroll. Use it unless you need a shorter spelling. -
ResponsiveChart- the minimal "fill the parent" wrapper: it measures its container with aResizeObserverand hands integerwidth/heightto the child. Give it anaspectto derive the height from the width (otherwise the measured height is used), withminWidth/minHeight/maxHeightclamps and an optional resizedebounce. -
ChartFrame- deprecated, useChartBox. It framed a chart image-style inside a fixed box (fit="fill"/fit="contain", nine-pointalign/verticalAlign,padding, letterboxbackground) - all of whichChartBoxdoes, with afitunion that is a superset of its two modes. It stays exported; just swap the tag name. -
ScrollChart- the "one dimension fills, the other scrolls" preset: setheightand the width fills, scrolling belowminWidth; setwidthand the height scrolls inside amaxHeightviewport. Use it for a 13-period trend or a wide table on a phone.
<ChartBox width={760} height={300} fit="scale" minWidth={680}>
{(w, h) => <TrendChart width={w} height={h} data={monthly} />}
</ChartBox>All of them are SSR-safe: nothing is drawn until the container has been measured,
so a chart never receives 0 or NaN and there is no layout jump.
KPIs, whole reports and the core chart set - 11 types (varianceColumn,
trend, structure, waterfall, stacked, line, area, scatter,
bubble, combo, tree) - are describable as plain serializable config
objects; the specialist charts (pie, the variance-waterfall family, small
multiples, ratio tree) are component-only. A report is data you can store,
diff and round-trip:
import { ConfiguredChart, validateChartConfig, validateReportConfig } from "ibcs-react";
// Whatever the JSON editor / API / database hands you - hence `unknown`.
const raw: unknown = { type: "varianceColumn", data: quarterlyRevenue, comparison: "PY" };
const result = validateChartConfig(raw); // { ok: true, config } | { ok: false, error }
if (result.ok) return <ConfiguredChart config={result.config} />; // config is a typed ChartConfigvalidateReportConfig does the same for a full ReportConfig before you hand
it to <Report />. Because configs are JSON, you can persist a dashboard, ship
it from a backend, or build a no-code editor on top.
Most chart libraries let you draw anything. This one can also check a config against the IBCS notation - the basis of ISO 24896 - and tell you exactly where it departs from the standard before it ships:
import { checkIbcs } from "ibcs-react";
const findings = checkIbcs(reportConfig);
// [] when fully conforming; otherwise { rule, severity, message, ... } items,
// e.g. a bare-string title flagged for not using a Who/What/When structure.Render the result with <ConformanceReport findings={findings} />. The encoded
rule set is exported as IBCS_RULES.
All colors and scenario styles live in tokens and are overridable per component
via the tokens prop (deep-merged):
import { StatementTable } from "ibcs-react";
<StatementTable lines={statement} tokens={{ color: { good: "#2e7d32", bad: "#c62828" } }} />;Set the theme once for a whole subtree with IbcsThemeProvider instead of
threading a tokens prop into every component:
import { IbcsThemeProvider, tokenPresets } from "ibcs-react";
<IbcsThemeProvider tokens={tokenPresets.cvd}>
<KpiCard label="Revenue" values={{ AC: 30.1e6, PY: 25.6e6 }} />
<StatementTable lines={statement} />
{/* nearest wins - this one chart departs from the theme */}
<TrendChart data={monthly} tokens={{ color: { bad: "#c62828" } }} />
</IbcsThemeProvider>;Resolution order, nearest first: a component's own tokens prop, merged onto the
nearest provider's theme, merged onto defaultTokens. Providers nest - an inner
provider composes onto the outer one, so a full preset and a one-line tweak are
the same operation. Building your own visual on ibcs-react/core?
useIbcsTokens(override?) resolves exactly what the built-ins resolve, so it
joins the same theme.
How cards and report blocks are framed is a token group as well (card):
framedCard is the default hairline card, flatCard is whitespace alone - the
IBCS SIMPLIFY look, and what a printed page wants:
import { Report, flatCard } from "ibcs-react";
<Report config={report} tokens={{ card: flatCard }} />;The eight ship presets are collected in tokenPresets, keyed by stable ids
(TokenPresetId) with display names in tokenPresetLabels - a typed theme
switcher is one map over Object.keys(tokenPresets) - and also exported one by
one:
tokenPresets key |
Label | Export |
|---|---|---|
default |
Default | defaultTokens |
ocean |
Ocean | oceanTokens |
azure |
Azure | azureTokens |
greenRed |
Green / Red | greenRedTokens |
vivid |
Vivid | vividTokens |
cvd |
CVD-safe | cvdTokens |
mono |
Mono / print | monoTokens |
dark |
Dark | darkTokens |
mergeTokens(override, base?) resolves a partial override into a full token set
(base defaults to defaultTokens, so presets compose), and IbcsTokensOverride
is the partial-token type to annotate your own theme objects with.
The root entry ships with a "use client" directive. The components use hooks
(hover, mount animation, container measurement), so importing ibcs-react from
an App Router page marks that import as client code and it just works - no
wrapper module of your own needed.
Server-side maths carries no such directive: import from ibcs-react/core in a
server component, a route handler, a cron job or a build script to precompute
statements, variances or a conformance check, then hand the plain data to a
client component that renders it. There's a runnable starter in
examples/nextjs.
The pure logic ships under a separate entry with zero React dependency:
import {
computeVariance,
formatValue,
statementToCSV,
checkIbcs,
defaultTokens,
} from "ibcs-react/core";Reuse it to build a Vue/Svelte view, precompute statements on the server, or run conformance checks in CI - no DOM required.
MIT © ibcs-react contributors
ibcs-react is an independent open-source project whose components are built in accordance with the IBCS® notation and ISO 24896. It is not affiliated with, endorsed by, or certified by the IBCS Association, HICHERT+FAISST, or ISO; conformance is self-assessed via the built-in
checkIbcs linter and a
work in progress. “IBCS” is a trademark of its respective owner.








