Area chart
A line with a quiet tint below, as a wash, a gradient or stacked bands with a surface gap.
One of 7 components in Charts
Variants
Details
What Area chart brings along, read from the registry and the source.
- Category
- Charts
- Area
- Elements
- Variants
- 3
- Version
- 1.0.0 · since 1.0.0
- File
- components/ui/area-chart.tsx
- Brings along
- utilskitchart
- npm packages
- None
- Languages
- Texts come through props
- Studio
- Adjust in code
- Build with AI
- Over MCP in Claude, Cursor, Copilot and Codex
Props
The AreaChartProps interface from components/ui/area-chart.tsx, with the comments from the code. Also takes every prop of ChartA11y.
datarequiredDatum[]Rows, one per x value, e.g. { day: '2026-09-14', visits: 412 }
xKeyrequiredstringKey of the x value. "2026-03" and "2026-03-14" are read as dates and formatted per language.
seriesrequiredChartSeries[]One area per series, in slot order (accent, grey, two accent tones)
formatoptionalIntl.NumberFormatOptionsValue format, e.g. { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }
xFormatoptionalIntl.DateTimeFormatOptionstooltipXFormatoptionalIntl.DateTimeFormatOptionsstackedoptionalbooleanStack the series to a total. Each band gets a tint and a top line in its series color.
filloptional'wash' | 'gradient'wash: an even, quiet tint. gradient: the tint fades towards the baseline.
curveoptional'smooth' | 'linear'heightoptionalnumberPlot height in px, axis labels included
legendoptionalbooleanLegend above the plot. Default: for two series and more.
xLabeloptionalstringHeader of the first table column
Accessibility
Read from the source of the component.
- Needs no client state. The server writes it in full, so it reads without JavaScript too.
Motion
- No animations, it stands still from the start.
Source
The file aiw add puts into your project. From then on it is yours.
Show the file, 134 lines
'use client';
import { useId } from 'react';
import { CartesianFrame, GlideDot, linePath, seriesColor, seriesOpacity, type ChartA11y, type ChartSeries, type Datum } from '@/components/ui/chart';
export interface AreaChartProps extends ChartA11y {
/** Rows, one per x value, e.g. { day: '2026-09-14', visits: 412 } */
data: Datum[];
/** Key of the x value. "2026-03" and "2026-03-14" are read as dates and formatted per language. */
xKey: string;
/** One area per series, in slot order (accent, grey, two accent tones) */
series: ChartSeries[];
/** Value format, e.g. { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 } */
format?: Intl.NumberFormatOptions;
xFormat?: Intl.DateTimeFormatOptions;
tooltipXFormat?: Intl.DateTimeFormatOptions;
/** Stack the series to a total. Each band gets a tint and a top line in its series color. */
stacked?: boolean;
/** wash: an even, quiet tint. gradient: the tint fades towards the baseline. */
fill?: 'wash' | 'gradient';
curve?: 'smooth' | 'linear';
/** Plot height in px, axis labels included */
height?: number;
/** Legend above the plot. Default: for two series and more. */
legend?: boolean;
/** Header of the first table column */
xLabel?: string;
className?: string;
}
/**
* Areas over time: a single tinted area, a gradient, or stacked bands.
* <AreaChart data={rows} xKey="day" series={[{ key: 'visits', label: 'Visits' }]} fill="gradient" />
*/
export function AreaChart({ data, xKey, series, format, xFormat, tooltipXFormat, stacked = false, fill = 'wash', curve = 'smooth', height = 260, legend, xLabel, label, summary, labels, animate, className }: AreaChartProps) {
const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
const num = (d: Datum, k: string) => (typeof d[k] === 'number' ? (d[k] as number) : null);
// Cumulative tops per series when stacked
const tops = series.map((_, si) => data.map((d) => series.slice(0, si + 1).reduce((a, s) => a + Math.max(0, num(d, s.key) ?? 0), 0)));
const all = stacked ? tops[tops.length - 1] ?? [] : data.flatMap((d) => series.map((s) => num(d, s.key))).filter((v): v is number => v !== null);
const extent: [number, number] = [Math.min(0, ...all), Math.max(0, ...all)];
const smooth = curve === 'smooth';
return (
<CartesianFrame
data={data}
xKey={xKey}
series={series}
format={format}
xFormat={xFormat}
tooltipXFormat={tooltipXFormat}
height={height}
scale="point"
extent={extent}
legend={legend}
legendShape={stacked ? 'rect' : 'line'}
crosshair
total={stacked}
xLabel={xLabel}
label={label}
summary={summary}
labels={labels}
animate={animate}
className={className}
marks={({ cat, val, animated, focus }) => (
<>
<defs>
{series.map((s, si) => (
<linearGradient key={s.key} id={`${uid}-g${si}`} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor={seriesColor(si, s.color)} stopOpacity={stacked ? 0.55 : 0.3} />
<stop offset="1" stopColor={seriesColor(si, s.color)} stopOpacity={stacked ? 0.22 : 0} />
</linearGradient>
))}
</defs>
{/* Draw from the last series down, so the first sits in front when not stacked */}
{[...series.keys()].reverse().map((si) => {
const s = series[si];
const color = seriesColor(si, s.color);
const style = animated ? ({ '--kitch-d': `${si * 120}ms` } as React.CSSProperties) : undefined;
const op = seriesOpacity(si, focus);
if (stacked) {
const upper: [number, number][] = data.map((_, i) => [cat(i), val(tops[si][i])]);
const lower: [number, number][] = data.map((_, i) => [cat(i), val(si ? tops[si - 1][i] : 0)]).reverse() as [number, number][];
const top = linePath(upper, smooth);
const bottom = linePath(lower, smooth).replace(/^M/, 'L');
return (
<g key={s.key} className="kitch-series" opacity={op}>
<path d={`${top} ${bottom} Z`} fill={fill === 'gradient' ? `url(#${uid}-g${si})` : color} fillOpacity={fill === 'gradient' ? 1 : 0.4} className={animated ? 'kitch-clip' : undefined} style={style} />
{/* Each band ends in a crisp line of its own color */}
<path d={top} fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" pathLength={1} className={animated ? 'kitch-draw' : undefined} style={style} />
</g>
);
}
const segments: [number, number][][] = [[]];
data.forEach((d, i) => {
const v = num(d, s.key);
if (v !== null) segments[segments.length - 1].push([cat(i), val(v)]);
else if (segments[segments.length - 1].length) segments.push([]);
});
return (
<g key={s.key} className="kitch-series" opacity={op}>
{segments
.filter((seg) => seg.length > 1)
.map((seg, k) => {
const top = linePath(seg, smooth);
const base = val(Math.max(extent[0], 0));
return (
<g key={k}>
<path
d={`${top} L${seg[seg.length - 1][0]} ${base} L${seg[0][0]} ${base} Z`}
fill={fill === 'gradient' ? `url(#${uid}-g${si})` : color}
fillOpacity={fill === 'gradient' ? 1 : si === 0 ? 0.12 : 0.1}
className={animated ? 'kitch-clip' : undefined}
style={style}
/>
<path d={top} fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" strokeDasharray={s.dashed ? '4 5' : undefined} pathLength={s.dashed ? undefined : 1} className={animated ? (s.dashed ? 'kitch-clip' : 'kitch-draw') : undefined} style={style} />
</g>
);
})}
</g>
);
})}
</>
)}
overlay={({ cat, val, shown, open, jump, focus }) =>
shown !== null &&
series.map((s, si) => {
const v = stacked ? tops[si][shown] : num(data[shown], s.key);
return v !== null && v !== undefined ? <GlideDot key={s.key} x={cat(shown)} y={val(v)} open={open} jump={jump} color={seriesColor(si, s.color)} opacity={seriesOpacity(si, focus)} /> : null;
})
}
/>
);
}Frequent questions
How do I install Area chart?
With aiw add @ai-website/area-chart. The command puts components/ui/area-chart.tsx into your project, the code is yours from then on. It brings along chart. It needs no further npm packages.
Which variants does Area chart have?
3 variants, Simple, Gradient and Stacked.
Can an AI assistant build with Area chart?
Yes. Through the MCP server of the aiw CLI, Claude, Cursor, GitHub Copilot, Windsurf, Codex and Gemini find Area chart with aiw_components and install it with aiw_add @ai-website/area-chart.