Acronis UIKit
Components

ChartWidget

The dashboard card a chart sits in — header, optional metric readout, and the body's empty/loading/error state.

Usage

import { ChartWidget } from '@acronis-platform/ui-react';

<ChartWidget header={{ title: 'Sessions', actions: <WidgetMenu /> }} className="h-[300px] w-[592px]">
  <AreaChart config={config} data={data} dataKeys={['sessions']} xKey="month" className="size-full" />
</ChartWidget>;

Every chart in the design is drawn inside the same card: a header with a title, an optional filter chip and the ⋯ actions menu, then an optional metric readout, then the plot. ChartWidget is that composition — and only that.

What it adds

One thing a Card legitimately doesn't know about: what the body shows while there's no plot. state renders a placeholder in place of the chart.

Everything else is Card's.

The header is Card's

header is typed CardHeaderProps and spread straight onto CardHeader, so the whole header surface works — including the parts this component never mentions.

<ChartWidget
  header={{
    title: 'Active alerts by severity',
    description: 'Grouped by severity',
    hasDescription: true,
    extras: <Tag variant="info">Last 6 months</Tag>,
    actions: <WidgetMenu />,
    isDraggable: true,
    hasRename: true,
    onRename: rename,
  }}
>
  {chart}
</ChartWidget>

Omit header for a header-less widget — a dashboard tile whose heading lives outside the card, or a chart embedded in a larger panel.

The per-type chart components stay card-less, so a chart is still usable outside a widget: inside a table cell, a popover, or a Metric's sparkline slot.

Metric readout

Pass a Metric to the metric slot to add a stats strip to the widget. The orientation prop controls how metric and chart are arranged:

  • vertical (default) — metric above the chart, full width. The metric takes its natural height; the chart fills the rest of the card body. Best for md/lg widths (592px, 896px).
  • horizontal — metric and chart side by side, each flex-[1_0_0] (equal halves). Best for sm width (288px) where stacking would leave too little height for the chart.
// vertical (default) — metric above the chart
<ChartWidget
  header={{ title: 'Backup success rate' }}
  metric={<Metric icon={<ChartPieIcon />} value="95" unit="%" />}
>
  <AreaChart config={config} data={data} dataKeys={['rate']} xKey="month" className="size-full" />
</ChartWidget>

// horizontal — metric left, chart right
<ChartWidget
  header={{ title: 'Backup success rate' }}
  orientation="horizontal"
  metric={
    <Metric
      icon={<ChartPieIcon />}
      value="95"
      unit="%"
      trend="up"
      trendValue="20%"
      supportingText="over 6 months"
    />
  }
>
  <AreaChart config={config} data={data} dataKeys={['rate']} xKey="month" className="size-full" />
</ChartWidget>

State

state swaps the plot for ChartState, keeping the header. The placeholder fills the same body the plot would, so the card keeps its size.

<ChartWidget header={{ title: 'Severity' }} state="loading" />

<ChartWidget header={{ title: 'Sessions' }} state="empty" variant="area" />

<ChartWidget
  header={{ title: 'Sessions' }}
  state="error"
  stateAction={<Button variant="ghost" onClick={refetch}>Try again</Button>}
/>

state="error" also gives the card its error border, so the border and the placeholder come from one prop rather than two. stateDescription overrides the default text.

It takes no size — the grid does

The Figma sets carry a size axis of sm / md / lg, but it only changes the frame width — 288 / 592 / 896. The height is the dashboard's business.

So the widget declares no size; it passes one down. The card is a full-height flex column, the header takes what it needs, and the body takes the rest — so a chart given size-full fills the whole remaining card.

<div className="grid grid-cols-2 gap-4 [grid-auto-rows:300px]">
  <ChartWidget header={{ title: 'Sessions' }}>
    <AreaChart className="size-full" />
  </ChartWidget>
</div>

Dropped into a parent with no definite height, the card hugs its content instead — so a standalone widget still works, with the chart bringing its own height.

bodyClassName covers the one remaining gap: a placeholder-only widget outside a sized cell, where neither the grid nor the content can give a height.

<ChartWidget header={{ title: 'Severity' }} state="empty" variant="bar" bodyClassName="h-[252px]" />

Accessibility

The widget adds no role of its own — it's a grouping surface, and the meaning lives in what it holds. To make each widget a landmark, use render, which is forwarded to Card:

<ChartWidget render={<section aria-label="Sessions" />} header={{ title: 'Sessions' }}>
  {chart}
</ChartWidget>

The accessible name has to come from you: the header's title is visible text inside the region, not a name for it, so a landmark without an aria-label (or an aria-labelledby pointing at the title) is an unnamed region.

The header's title is the widget's visible name, so a chart inside shouldn't repeat it in its own accessible name. While state is set, ChartState is the live region (role="status", or role="alert" for errors).

An icon-only control in actions needs its own aria-label — the ⋯ menu has no visible text.

Edit on GitHub

On this page