Area Chart

A composable area chart with gradient fills, tooltips, and hover interactions

Installation

pnpm dlx shadcn@latest add @bklit/area-chart

Usage

The Area Chart uses the same composable API as the Line Chart. See the charts gallery for interactive examples.

import { AreaChart, Area, Grid, XAxis, ChartTooltip } from "@bklitui/ui/charts";

const data = [
  { date: new Date("2025-01-01"), revenue: 12000, costs: 8500 },
  { date: new Date("2025-01-02"), revenue: 13500, costs: 9200 },
  // ... more data
];

export default function RevenueChart() {
  return (
    <AreaChart data={data}>
      <Grid horizontal />
      <Area dataKey="revenue" fill="var(--chart-line-primary)" />
      <Area dataKey="costs" fill="var(--chart-line-secondary)" />
      <XAxis />
      <ChartTooltip />
    </AreaChart>
  );
}

Components

AreaChart

The root component that provides context to all children. It shares the same props as LineChart.

PropTypeDefaultDescription
dataRecord<string, unknown>[]requiredArray of data points
xDataKeystring"date"Key in data for x-axis values
marginPartial<Margin>{ top: 40, right: 40, bottom: 40, left: 40 }Chart margins
animationDurationnumber1100Clip-reveal duration in ms (cubic-bezier(0.85, 0, 0.15, 1))
status"loading" | "ready""ready"Loading ↔ ready choreography on one chart instance
loadingLabelstringCentered shimmer label while status="loading" ("" hides it)
yDomainTweenbooleantrueAnimate y-domain when status or target domain changes
yDomainTweenDurationnumber500Y-domain tween duration in ms
xDomain[Date, Date]Visible x-range for brush zoom
xDomainSlotCountnumberFull dataset length for x-scale padding when xDomain is set
tweenYDomainOnXDomainChangebooleanfalseTween y-domain when the brush changes the visible x-range
aspectRatiostring"2 / 1"CSS aspect ratio
classNamestring""Additional CSS class
styleCSSPropertiesInline container styles (e.g. fixed height for a brush strip)

Area

Renders a filled area on the chart with a gradient fill.

PropTypeDefaultDescription
dataKeystringrequiredKey in data for y values
yAxisIdstring | number"left"Y-scale group for biaxial charts (pair with YAxis)
fillstringvar(--chart-line-primary)Gradient fill color
fillOpacitynumber0.4Fill opacity at the top
strokestringSame as fillLine stroke color
strokeWidthnumber2Line stroke width
curveCurveFactorycurveMonotoneXD3 curve function
animatebooleantrueEnable grow animation
showLinebooleantrueShow stroke line on top
showHighlightbooleantrueShow highlight on hover
gradientToOpacitynumber0Opacity at bottom of gradient
fadeEdgesbooleanfalseFade area fill at left/right edges
showMarkersbooleanfalseRender scatter-style ring markers at each point
loadingStrokestringvar(--foreground)Pulse stroke color while chart is loading
loadingStrokeOpacitynumber0.5Pulse stroke opacity while chart is loading
markersSeriesPointMarkerStyleMarker styling (same options as Scatter)

PatternArea

Renders a filled area using an SVG pattern (url(#id)). Define the pattern (e.g. PatternLines) as a child of AreaChart, then pair PatternArea with an Area that has fillOpacity={0} for the stroke line.

PropTypeDefaultDescription
dataKeystringrequiredKey in data for y values
fillstringrequiredFill color or pattern URL (e.g. url(#pattern-id))
curveCurveFactorycurveMonotoneXD3 curve function

Grid

Renders grid lines.

PropTypeDefaultDescription
horizontalbooleantrueShow horizontal lines
verticalbooleanfalseShow vertical lines
numTicksRowsnumber5Number of horizontal lines
numTicksColumnsnumber10Number of vertical lines
strokestringvar(--chart-grid)Line color while ready
loadingStrokestringGrid stroke while loading chrome is active
strokeDasharraystring"4,4"Dash pattern
shimmerbooleanfalseAnimate a shimmer band across horizontal grid lines
shimmerStrokestringcolor-mix(…) on --foreground at 68%Shimmer band color and opacity
shimmerLengthnumber140Shimmer band width in pixels
shimmerSpeednumber1Shimmer speed multiplier when sync is off (higher = faster)
shimmerSyncbooleanfalseMatch shimmer timing to the line pulse (2.2s cycle + 280ms pause)

Background

Pattern fill for the plot area when you omit Grid. See the Background utility and Pattern Background examples on the area chart gallery.

YAxis

Value labels on the left or right. See Y Axis for yAxisId, orientation, and biaxial usage.

XAxis

Renders x-axis labels that fade when the crosshair passes.

PropTypeDefaultDescription
numTicksnumber5Number of tick labels when tickMode is "domain"
tickerHalfWidthnumber50Fade radius for labels
tickMode"domain" | "data""domain""domain" for evenly spaced ticks; "data" for one label per row

ChartTooltip

Renders the tooltip with crosshair, dots, and content box.

PropTypeDefaultDescription
showDatePillbooleantrueShow animated date ticker
showCrosshairbooleantrueShow vertical crosshair
showDotsbooleantrueShow dots on series
indicatorColorstring | (point) => stringCrosshair and dot color
indicatorDasharraystringDash pattern for the crosshair (e.g. "4,4")
indicatorFadeEdges"both" | "top" | "bottom" | "none""both"Vertical crosshair fade
indicatorFadeLengthnumber10Fade size (% of height)
matchCrosshairbooleanfalsePanel uses crosshair spring when true
dampingnumber20Panel follow when matchCrosshair={false}; 0 = instant
content(props) => ReactNode-Custom content renderer
rows(point) => TooltipRow[]-Custom row generator

Brush zoom

See the Brush utility docs for ChartBrushLayout and ChartBrush props.

Wrap the main chart in ChartBrushLayout, render a simplified mini chart in brushStrip, and add ChartBrush as a child of that strip. Pass xDomain, xDomainSlotCount, and tweenYDomainOnXDomainChange to the main AreaChart so the y-scale adapts as users pan and resize the brush.

<ChartBrushLayoutdata={data}enabledheight={72}brushStrip={(layout) => (  <AreaChart data={data} animationDuration={0} status="ready">    <Area dataKey="value" fillOpacity={0.15} animate={false} />    <ChartBrush      initialSelection={layout.brushSelection ?? undefined}      onSelectionChange={layout.onBrushSelectionChange}    />  </AreaChart>)}>{(layout) => (  <AreaChart    data={data}    xDomain={layout.xDomain}    xDomainSlotCount={layout.xDomainSlotCount}    tweenYDomainOnXDomainChange    yDomainTween  >    <Grid horizontal />    <Area dataKey="value" fillOpacity={0.35} />    <XAxis />    <ChartTooltip />  </AreaChart>)}</ChartBrushLayout>

Open Studio with brush enabled to tune strip height, blur, and selection pattern.

Loading state

Drive loading and ready from your data layer with a single AreaChart — one Grid, one Area, no component swap. Set status="loading" while fetching; switch to "ready" when data resolves.

Loading → ready: pulse loop on skeleton data → pulse finishes its grow, then flows out right → loading label drifts down 30px, blurs, and fades → grid y-domain tween (500ms) → clip-path reveal (cubic-bezier(0.85, 0, 0.15, 1)) → interaction enabled.

Ready → loading: ready area conceals to the right → grid y-domain tween → pulse loop and shimmer resume.

Pair Grid stroke / loadingStroke with shimmer props. Pair Area loadingStroke props. Use loadingLabel on AreaChart for centered shimmer text via @ncdai/shimmering-text.

Loading revenue…
const [status, setStatus] = useState<"loading" | "ready">("loading");<AreaChartdata={data}status={status}loadingLabel="Loading revenue…"yDomainTween><Grid  horizontal  loadingStroke="color-mix(in oklch, var(--chart-grid) 50%, transparent)"  shimmer  shimmerSync  stroke="var(--chart-grid)"/><Area  dataKey="revenue"  fadeEdges  fill="var(--chart-line-primary)"  fillOpacity={0.35}  loadingStroke="var(--foreground)"  loadingStrokeOpacity={0.5}  strokeWidth={2}/></AreaChart>

Toggle Loading / Ready in the preview to replay the transition. When target data spans a different y-range than the skeleton, yDomainTween morphs the scale before the area reveals.

Studio

Open Studio in loading mode and set State to Loading. The components tree exposes Grid, Label, and Area:

LayerControls
GridGrid and shimmer color pickers, shimmer toggle, band length, Animation (sync with pulse, speed when unsynced)
LabelShimmer label text
AreaPulse stroke color and opacity

Data and animation panels stay collapsed in loading mode; scramble data is disabled. See the area chart gallery (Loading example).

Add @ncdai to your registry config (https://chanhdai.com/r/{name}.json) — installing @bklit/area-chart pulls in @ncdai/shimmering-text automatically.

Dashed tail

Set dashFromIndex on Area to draw a solid stroke through one data point, then a dashed segment through the end of the series. Useful when the final period is still in progress (e.g. yesterday → today).

dashFromIndex is inclusive — dashing starts at that row and continues through the last point. The dashed segment follows the same curved path as the solid stroke and respects the stroke gradient fade from fadeEdges.

PropTypeDefaultDescription
dashFromIndexnumberInclusive data index where the dashed tail begins
dashArraystring"6,4"SVG stroke-dasharray pattern for the tail segment
<Area
  dataKey="visitors"
  dashFromIndex={5}
  dashArray="6,4"
  fill="var(--chart-line-primary)"
  fillOpacity={0.35}
/>

Markers

Add markers to annotate specific dates on the chart:

import {
  AreaChart,
  Area,
  ChartTooltip,
  ChartMarkers,
  MarkerTooltipContent,
  useActiveMarkers,
  type ChartMarker,
} from "@bklitui/ui/charts";

const markers: ChartMarker[] = [
  {
    date: new Date("2025-01-05"),
    icon: "🚀",
    title: "v1.2.0 Released",
    description: "New chart animations",
  },
];

function MyChart({ data }) {
  return (
    <AreaChart data={data}>
      <Area dataKey="revenue" fill="var(--chart-line-primary)" />
      <ChartMarkers items={markers} />
      <ChartTooltip>
        <MarkerContent markers={markers} />
      </ChartTooltip>
    </AreaChart>
  );
}

function MarkerContent({ markers }) {
  const activeMarkers = useActiveMarkers(markers);
  if (activeMarkers.length === 0) return null;
  return <MarkerTooltipContent markers={activeMarkers} />;
}

ChartMarker Interface

interface ChartMarker {
  date: Date;
  icon: React.ReactNode;
  title: string;
  description?: string;
  content?: React.ReactNode;
  color?: string;
  onClick?: () => void;
  href?: string;
  target?: "_blank" | "_self";
}

ChartMarkers Props

PropTypeDefaultDescription
itemsChartMarker[]requiredArray of markers
sizenumber28Marker circle size
showLinesbooleantrueShow vertical guide lines
animatebooleantrueAnimate markers on entrance

Segment Selection

Add click-drag and touch segment selection with composable components. The area highlight automatically shows the selected path segment.

Basic Usage

import {
  AreaChart,
  Area,
  Grid,
  XAxis,
  ChartTooltip,
  SegmentBackground,
  SegmentLineFrom,
  SegmentLineTo,
} from "@bklitui/ui/charts";

<AreaChart data={data}>
  <Grid horizontal />
  <Area dataKey="revenue" fill="var(--chart-line-primary)" />
  <SegmentBackground />
  <SegmentLineFrom />
  <SegmentLineTo />
  <XAxis />
  <ChartTooltip />
</AreaChart>

Use SegmentBackground, SegmentLineFrom, and SegmentLineTo independently — you do not need all three. Boundary lines support variant="dashed" | "solid" | "gradient".

Reading Selection Data

Use the useChart hook inside a child component to read the active selection:

import { useChart } from "@bklitui/ui/charts";

function SelectionStats({ onSelectionChange }) {
  const { selection, data, xAccessor } = useChart();

  useEffect(() => {
    if (!selection?.active) {
      onSelectionChange(null);
      return;
    }

    const startPoint = data[selection.startIndex];
    const endPoint = data[selection.endIndex];
    onSelectionChange({ startPoint, endPoint });
  }, [selection, data, xAccessor, onSelectionChange]);

  return null;
}

SegmentBackground

PropTypeDefaultDescription
fillstringvar(--chart-segment-background)Fill color for the selected region

SegmentLineFrom / SegmentLineTo

PropTypeDefaultDescription
strokestringvar(--chart-segment-line)Line color
strokeWidthnumber1Line width
variant"dashed" | "solid" | "gradient""dashed"Line style

Theming

The Area Chart uses the same CSS variables as the Line Chart:

:root {
  --chart-background: oklch(1 0 0);
  --chart-foreground: oklch(0.145 0.004 285);
  --chart-foreground-muted: oklch(0.55 0.014 260);
  --chart-line-primary: oklch(0.623 0.214 255);
  --chart-line-secondary: oklch(0.705 0.015 265);
  --chart-crosshair: oklch(0.4 0.1828 274.34);
  --chart-grid: oklch(0.9 0 0);
  --chart-tooltip-foreground: oklch(0.985 0 0);
  --chart-tooltip-muted: oklch(0.65 0.01 260);
  --chart-marker-background: oklch(0.97 0.005 260);
  --chart-marker-border: oklch(0.85 0.01 260);
  --chart-marker-foreground: oklch(0.3 0.01 260);
  --chart-marker-badge-background: oklch(0 0 0);
  --chart-marker-badge-foreground: oklch(1 0 0);
  --chart-segment-background: oklch(0.5 0 0 / 0.06);
  --chart-segment-line: oklch(0.5 0 0 / 0.25);
}

.dark {
  --chart-background: oklch(0.145 0 0);
  --chart-foreground: oklch(0.45 0 0);
  --chart-crosshair: oklch(0.45 0 0);
  --chart-grid: oklch(0.25 0 0);
  --chart-marker-background: oklch(0.25 0.01 260);
  --chart-marker-border: oklch(0.4 0.01 260);
  --chart-marker-foreground: oklch(0.9 0 0);
  --chart-marker-badge-background: oklch(1 0 0);
  --chart-marker-badge-foreground: oklch(0.15 0 0);
  --chart-segment-background: oklch(1 0 0 / 0.06);
  --chart-segment-line: oklch(1 0 0 / 0.25);
}

Dependencies

This component requires the same packages as the Line Chart:

pnpm add @visx/shape @visx/curve @visx/scale @visx/gradient @visx/responsive @visx/event @visx/grid d3-array motion react-use-measure