Conformance
checkIbcs is the library's own IBCS-notation linter - lint a chart, KPI or report config in CI and get findings by rule id and JSON path.
Most chart libraries only let you draw. This one can also check a config against the IBCS notation rules it implements and tell you where the config departs from them - before it ships.
A notation linter
The same JSON config that renders a chart, KPI or report can also be
linted. Call checkIbcs(config) and you get back an array of IbcsFinding
objects: non-linear chart types, unstructured titles, missing data, wrong
favorability for cost measures, and so on. An empty array means the config
passes every rule the linter implements. It is pure logic - zero React, zero
dependencies, JSON in and JSON out - so it runs in a unit test, a CI gate or an
authoring tool.
import { checkIbcs, ConformanceReport } from "ibcs-react";
// A config that breaks the rules: pie isn't a linear chart type, the title is
// a bare string (not Who/What/When), and there's no data to plot.
const config = { type: "pie", title: "Revenue 2026", data: [] };
const findings = checkIbcs(config);
// → [
// { rule: "linear-chart-type", severity: "error", path: "type", message: … },
// { rule: "data-present", severity: "error", path: "data", message: … },
// { rule: "structured-title", severity: "warning", path: "title", message: … },
// ]
// An empty array means no rule was violated.
// Or render the findings - pass a target to lint on the fly…
<ConformanceReport target={config} />
// …or hand it findings you computed yourself:
<ConformanceReport findings={findings} />The linter runs at build time, not as a house-style debate. A config either passes the IBCS notation rules the linter implements, or it lists exactly what to change - by rule id and JSON path.
Lint the JSX path
Most apps author charts as JSX, not JSON configs - the props are the same
shapes minus the type discriminator, which the component name carries.
checkIbcsProps maps the name back and runs the same rules, so a dashboard
written the way getting started teaches is lintable
in a unit test without restructuring:
import { checkIbcsProps } from "ibcs-react";
// <VarianceColumnChart data={productLines} comparison="PY" variance="abs" />
test("the revenue chart follows IBCS notation", () => {
expect(
checkIbcsProps("VarianceColumnChart", {
data: productLines,
comparison: "PY",
variance: "abs",
title: { who: "ACME", what: "Revenue (€k)", when: "2026" },
}),
).toEqual([]);
});Render-only props (width, tokens, onSelect, …) carry no rules and are
ignored; lint-only declarations (measureKind) can ride along. KpiCard
props already are a KpiConfig and lint directly. The specialised variance
charts lint as the linear family they render - and checkIbcsProps("PieChart", …)
flags the pie, exactly as it should.
Live playground
The config below deliberately breaks three rules: a pie (a non-linear chart
type), a bare string title instead of a structured Who/What/When, and an
empty data array. Edit the JSON and watch the findings update live - the same
ConformanceReport component renders the result.
- errorchart type "pie" is non-linear - IBCS forbids area/angle encodings because they distort comparison. Use one of "varianceColumn", "trend", "structure", "waterfall", "stacked", "line", "area", "scatter", "bubble", "combo", "tree". (type) · linear-chart-type
- errorchart has no data - nothing to plot. (data) · data-present
- warningtitle "Revenue 2026" is a bare string - use a Who/What/When structured title (ISO 24896 SAY). (title) · structured-title
Out of the box this example produces two errors (non-linear chart type, no
data) and one warning (bare-string title). Switch "pie" to a linear type
such as "varianceColumn", give it a data row and replace the title with a
structured { who, what, when } object - the list empties and the report says
so. Deleting the title is not a way out: a chart or report with no title
is flagged at the same severity (ISO 24896 SAY requires one), and an unknown
type string gets a did-you-mean plus the list of valid values.
The rule catalog
Every finding references a rule id from IBCS_RULES, a serializable catalog
you can surface in your own UI legends or docs. The full set the linter
encodes:
| Rule id | Severity | What it checks |
|---|---|---|
| linear-chart-type | error | Linear chart types only IBCS permits only linear charts (column, bar, line, area, scatter, bubble, waterfall families). Pie, gauge, radar and similar are forbidden because area/angle encodings distort comparison. Unknown type strings are flagged under this rule too, with the valid values listed. |
| structured-title | warning | Structured Who / What / When title ISO 24896 SAY: a title states Who (entity), What (measure + unit) and When (period) on separate lines, kept apart from the interpretive key message. A bare string title loses that structure - and a chart or report with NO title at all says nothing. Both are flagged. |
| show-variance | info | Show a variance IBCS reports compare actuals against a scenario (AC vs PY / PL / FC). A chart or KPI with no comparison shows a number without a yardstick. |
| data-present | error | Data must be present A chart or KPI with no data cannot be rendered or checked. |
| block-type | error | Known report block type Report blocks must be one of kpi, chart, statement, table or text. |
| cost-favorability | warning | Correct favorability for cost measures For cost / expense / tax measures a higher value is unfavorable. Set higherIsBetter:false so impact (favorability) coloring shows overruns in red, not green. Detected from an explicit measureKind:"cost" declaration, or heuristically from the title / KPI label. |
| shared-scale | info | Consistent scaling across same-unit charts Charts of the same unit should share a value scale (and a zero baseline) so bar lengths are comparable across the report. |
| ratio-units | info | Percentage-point deltas for ratio measures A percentage MEASURE (margin, rate, share) moves in percentage points, not percent-of-percent. A KPI formatted with suffix "%" should declare unit:"ratio" so its delta renders as +0.6pp instead of a misleading relative +0.9%. |
| input-shape | info | Recognizable config shape The linter inspects ChartConfig, KpiConfig and ReportConfig shapes. Other values can't be checked. |
import { IBCS_RULES } from "ibcs-react";
IBCS_RULES.map((rule) => `${rule.id} (${rule.severity}) - ${rule.title}`);Declare what the measure is
The cost-favorability rule normally detects cost measures from the title or
KPI label ("Operating expenses", "Tax", …) - structured titles included. When
the wording doesn't give it away, declare it: measureKind: "cost" on a chart
or KPI config makes the linter insist on higherIsBetter: false no matter
what the title says, and measureKind: "revenue" silences the heuristic for
measures that merely sound like costs ("Cost recovery revenue"). The
declaration is linter-only - rendering still follows higherIsBetter.
checkIbcs({ type: "varianceColumn", measureKind: "cost", data, title });
// → cost-favorability warning until the config sets higherIsBetter: falseWhat a finding looks like
Each IbcsFinding carries a rule id, a severity ("error", "warning" or
"info"), a human-readable message and an optional path locating the
offending value (for example blocks[2].config.type).
| Field | Type | Meaning |
|---|---|---|
rule | string | Rule id - matches an entry in IBCS_RULES. |
severity | "error" | "warning" | "info" | Errors mark a broken rule; warnings and info are advisory. |
message | string | Human-readable description of the departure. |
path | string? | JSON-ish path to the offending value, e.g. blocks[2].title. |
Scope
checkIbcs is the library's own check against the IBCS notation rules listed
above, implemented from the publicly described notation. It is not a
certification, an audit or an assessment by IBCS or ISO, and a clean run says
nothing beyond "none of these rules were violated". Rules the linter does not
implement - and anything about the correctness of your numbers - remain your
call.
Pair it with rendering
The linter works on the very same configs the components render, so you can lint in CI and render in the app from one source of truth.
import { checkIbcs } from "ibcs-react";
test("dashboard configs break no notation rule", () => {
for (const config of dashboardConfigs) {
const errors = checkIbcs(config).filter((f) => f.severity === "error");
expect(errors).toEqual([]);
}
});- IBCS & ISO 24896 - what the rules mean.
- Components - the config shapes the linter understands.