ChartContainer wraps any Semiotic chart in a production-ready shell with a title, subtitle, toolbar actions, loading and error states, and a streaming status badge. It's fully theme-aware via ThemeProvider.
Basic Usage
Wrap any chart in ChartContainer with a title and subtitle. Enable export and fullscreen via the actions prop.
Monthly Revenue
USD thousands, 2024
JSX
import { ChartContainer, LineChart } from "semiotic" <ChartContainer title="Monthly Revenue" subtitle="USD thousands, 2024" actions={{ export: true, fullscreen: true, dataSummary: true }} > <LineChart data={data} lineBy="id" xAccessor="x" yAccessor="y" curve="monotoneX" /> </ChartContainer>
Custom Controls
Pass any React content to controls to render it in the toolbar before the built-in action buttons.
JSX
const [year, setYear] = useState("2024") const dataByYear = { 2024: [{ category: "Q1", value: 120 }, { category: "Q2", value: 180 }, ...], 2023: [{ category: "Q1", value: 95 }, { category: "Q2", value: 140 }, ...], } <ChartContainer title="Quarterly Sales" actions={{ export: { format: "png", filename: "sales" } }} controls={ <select value={year} onChange={(e) => setYear(e.target.value)}> <option value="2024">2024</option> <option value="2023">2023</option> </select> } > <BarChart data={dataByYear[year]} categoryAccessor="category" valueAccessor="value" /> </ChartContainer>
Data Summary
Enable actions.dataSummary to add a toolbar button that reveals a statistical summary of the chart's data — field ranges, means, and 5 sample rows. This is useful for everyone: screen reader users get it automatically, but sighted users can also inspect what data the chart is actually rendering. Click the bar-chart icon in the toolbar below.
Quarterly Sales
Click the summary icon to inspect the data
JSX
// Data summary action — shows stats + sample rows in an overlay above the plot <ChartContainer title="Quarterly Sales" actions={{ dataSummary: true, export: true }} > <BarChart data={data} categoryAccessor="category" valueAccessor="value" /> </ChartContainer> // The summary panel shows something like: // "4 data points. category: Q1, Q2, Q3, Q4. value: 95 to 210, mean 151.25." // + a 4-row sample table // Works with all chart types — XY, ordinal, network, geo. // For screen readers, the same data is always available via a // hidden button (accessibleTable={true} by default).
Notifications
The notifications prop surfaces chart-level notices that have no single mark to anchor to — machine findings (aData Pitfalls report entry about the whole chart, an unplaceabledata-qualityresult) and any custom user-authored notice. They collapse into a single toolbar bell with a count badge: the bell adopts the icon and color of the most severe visible notice, so severity reads at a glance. Clicking the bell opens a popover with the full dismissible cards — an overlay, so a notice arriving or being dismissed never reflows the chart body. Levels map to the theme’s semantic role colors, and a screen-reader-only aria-live="polite"region announces the current count and most-severe level, so notices that arrive while streaming are still voiced even with the popover collapsed.
Customer satisfaction
The error finding came back from a datapitfalls scan; the info note is pinned (dismissible: false)
3 chart notifications, most severe: error
JSX
<ChartContainer title="Customer satisfaction" notifications={[ { id: "truncated-axis", // stable id — keys dismissal level: "error", // "info" | "success" | "warning" | "error" | "neutral" title: "Truncated axis", message: "Start the bar axis at zero.", source: "datapitfalls · Graphical Gaffes", // small origin tag }, { id: "methodology", level: "info", message: "Q3 values are restated after the fiscal-calendar change.", dismissible: false, // pinned — no dismiss button }, ]} onNotificationDismiss={(notification, index) => track(notification.id)} > <BarChart data={data} categoryAccessor="category" valueAccessor="value" /> </ChartContainer> // Feeding machine findings straight in — e.g. chart-level entries from a // datapitfalls report (findings that name the whole chart, not one mark): const notifications = report.findings .filter((finding) => !anchorable(finding)) .map((finding) => ({ id: finding.ruleId, level: finding.severity, // info | warning | error map 1:1 title: finding.name, message: finding.remediation, source: `datapitfalls · ${finding.domain}`, }))
Dismissal is tracked internally, keyed by id (falling back to array index), so a re-render with the same list keeps dismissed entries dismissed — pass onNotificationDismiss to sync your own store or telemetry. Mark-level findings belong on the plot asannotations; notifications are for everything that describes the chart as a whole. See theChart Clinic example for the two surfaces working together on a real audit report.
Loading & Error States
Toggle loading to show a skeleton placeholder, or seterror to display an error message in place of the chart.
Failed to fetch data from API.
JSX
// Loading skeleton <ChartContainer title="CPU Load" loading={isLoading}> <LineChart data={data} xAccessor="x" yAccessor="y" /> </ChartContainer> // Error state <ChartContainer title="CPU Load" error="Failed to fetch data from API."> <LineChart data={data} xAccessor="x" yAccessor="y" /> </ChartContainer> // Error boundary (catches render crashes) <ChartContainer title="CPU Load" errorBoundary> <LineChart data={data} xAccessor="x" yAccessor="y" /> </ChartContainer>
Individual charts also support built-in loading andemptyContent props. SeeChart States for full documentation on empty, loading, and error patterns.
Status Badge
Use the status prop to show a streaming status indicator. Works well with realtime charts.
Sensor Feed
Temperature readings
JSX
<ChartContainer title="Sensor Feed" status="live" // "live" | "stale" | "paused" | "error" | "static" > <RealtimeLineChart ref={chartRef} timeAccessor="time" valueAccessor="temp" /> </ChartContainer>
Imperative Handle
Use a ref to call export() or toggleFullscreen() programmatically.
JSX
import { useRef } from "react" import { ChartContainer } from "semiotic" function Dashboard() { const chartRef = useRef(null) const downloadAll = async () => { await chartRef.current.export({ format: "png", scale: 2 }) } return ( <> <button onClick={downloadAll}>Download PNG</button> <ChartContainer ref={chartRef} title="Revenue"> <LineChart data={data} xAccessor="x" yAccessor="y" /> </ChartContainer> </> ) }
Dark Theme
ChartContainer reads CSS custom properties fromThemeProvider, so it adapts automatically. All examples on this page already follow the site theme — toggle dark/light mode in the header to see it.
Always Dark
Pinned to dark theme regardless of site mode
JSX
import { ThemeProvider, ChartContainer, LineChart } from "semiotic" // Pin to dark theme explicitly <ThemeProvider theme="dark"> <ChartContainer title="Dark Mode" actions={{ export: true }} status="live"> <LineChart data={data} xAccessor="x" yAccessor="y" /> </ChartContainer> </ThemeProvider> // Or follow a dynamic theme <ThemeProvider theme={isDark ? "dark" : "light"}> <ChartContainer title="Adaptive"> <LineChart data={data} xAccessor="x" yAccessor="y" /> </ChartContainer> </ThemeProvider>
Props
| Prop | Type | Required | Default | Description |
|---|
title | string | — | undefined | Chart title displayed in the header bar. |
subtitle | string | — | undefined | Subtitle / description displayed below the title. |
children | ReactNode | Yes | - | Any Semiotic chart component(s). |
width | number | string | — | "100%" | Width of the container. Passed to child chart if not set on child. |
height | number | — | 400 | Height of the chart area (excluding header). |
actions | { export?, fullscreen?, copyConfig?, dataSummary? } | — | undefined | Built-in action buttons. Each can be true or false. export also accepts { format, scale, filename }. dataSummary shows a statistical summary + sample rows panel. |
controls | ReactNode | — | undefined | Additional controls rendered in the toolbar before built-in action buttons. |
notifications | ChartNotification[] | — | undefined | Chart-level notices surfaced as a severity-colored toolbar bell with a count badge; clicking it opens a popover with the dismissible cards (an overlay, so arriving/dismissing notices never reflow the plot). Each entry: { id?, level?, title?, message, source?, dismissible? }. Levels (info | success | warning | error | neutral) map to the theme's semantic role colors; the bell adopts the icon + color of the most severe visible notice, and an sr-only aria-live region announces the count + severity. |
onNotificationDismiss | (notification, index) => void | — | undefined | Called when a notification's dismiss button is clicked. Dismissal is tracked internally (keyed by notification.id, falling back to array index) whether or not this is provided. |
loading | boolean | — | false | Shows a pulsing skeleton placeholder instead of chart children. |
error | string | ReactNode | — | undefined | Shows an error message in place of chart children. |
errorBoundary | boolean | — | false | Wraps children in ChartErrorBoundary to catch render errors. |
status | "live" | "stale" | "paused" | "error" | "static" | — | undefined | Status badge displayed in the toolbar after action buttons. Green for live, red for stale/error, yellow for paused, gray for static. |
className | string | — | undefined | CSS class for the outer container. |
style | CSSProperties | — | undefined | Inline style overrides for the outer container. |
ref | Ref<ChartContainerHandle> | — | - | Imperative handle with export(), toggleFullscreen(), and element properties. |
- Theming — ThemeProvider controls the CSS custom properties that ChartContainer reads
- Accessibility — data summary, keyboard navigation, screen reader support
- Styling — styling the chart marks inside the container
- Realtime Encoding — use
status with streaming charts