Beautiful charts. Built using LayerChart. Copy and paste into your apps.

Bar Chart - Interactive
Showing total visitors for the last 3 months

Introducing Charts. A collection of chart components that you can copy and paste into your apps.

Charts are designed to look great out of the box. They work well with the other components and are fully customizable to fit your project.

About

Charts are drawn by LayerChart, a Svelte charting library built on d3. The chart component ports shadcn/ui's ChartConfig, tooltip, and legend to LayerChart. ChartContainer turns a config into colors, and ChartTooltip and ChartLegend read the same config for labels, icons, and colors.

The examples on this page port the LayerChart charts of shadcn-svelte to the Sveltery components. They follow the charts of shadcn/ui, and the licenses are in THIRD_PARTY_NOTICES.md.

Component

We designed the chart component with composition in mind. You build your charts with LayerChart components and bring in ChartContainer, ChartTooltip, and ChartLegend only where you need them.

<script lang="ts">
  import { BarChart } from 'layerchart';
  import { ChartContainer, ChartTooltip, type ChartConfig } from '$lib/components/ui/chart';

  const data = [
    { month: 'January', value: 186 },
    { month: 'February', value: 305 }
  ];
  const config = { value: { label: 'Visitors', color: 'var(--chart-1)' } } satisfies ChartConfig;
</script>

<ChartContainer {config} aria-label="Visitors by month">
  <BarChart {data} x="month" series={[{ key: 'value', color: 'var(--color-value)' }]}>
    {#snippet tooltip()}
      <ChartTooltip />
    {/snippet}
  </BarChart>
</ChartContainer>

We do not wrap LayerChart. You are not locked into an abstraction, and when LayerChart ships a new version, you follow its upgrade path to update your charts.

The components are yours.

Installation

Install the dependencies

pnpm add cn layerchart

Many examples also import d3-scale and d3-shape, which LayerChart already depends on. Install them when you import from them in your own code.

Copy the component

Copy the following files into src/lib/components/ui/chart.

src/lib/components/ui/chart/ChartContainer.svelte
<script lang="ts">
  import { on } from 'svelte/events';
  import { cn } from 'cn';
  import type { ChartState } from 'layerchart';
  import ChartStyle from './ChartStyle.svelte';
  import { setChartContainer, type ChartRow } from './context.js';
  import type { ChartContainerProps } from './types.js';

  const uid = $props.id();
  let { id, config, class: classProp, children, ...props }: ChartContainerProps = $props();

  const chartId = $derived(`chart-${id ?? uid}`);
  let tooltipChart = $state.raw<ChartState<ChartRow>>();

  setChartContainer({
    get config() {
      return config;
    },
    registerTooltip(chart) {
      tooltipChart = chart;
      return () => {
        if (tooltipChart === chart) tooltipChart = undefined;
      };
    },
  });

  // LayerChart has no keyboard tooltip. The container steps through the chart's rows and shows each one through the chart's tooltip.
  function keyboard(node: HTMLElement) {
    const chart = tooltipChart;
    if (!chart) return;
    const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 } as const;
    const offKeydown = on(node, 'keydown', (event) => {
      const rows = chart.flatData;
      const last = rows.length - 1;
      if (event.key === 'Escape') return chart.tooltip.hide();
      const current = rows.indexOf(chart.tooltip.data);
      const next =
        event.key === 'Home' ? 0
        : event.key === 'End' ? last
        : event.key in step ? current + step[event.key as keyof typeof step]
        : undefined;
      if (next === undefined) return;
      event.preventDefault();
      chart.tooltip.show({ data: rows[Math.min(Math.max(next, 0), last)] });
    });
    const offBlur = on(node, 'blur', () => chart.tooltip.hide());
    return () => {
      offKeydown();
      offBlur();
    };
  }
</script>

<!-- The tabindex and role exist only while a tooltip is registered. -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<div
  data-slot="chart"
  data-chart={chartId}
  tabindex={tooltipChart ? 0 : undefined}
  role={tooltipChart ? 'application' : undefined}
  aria-roledescription={tooltipChart ? 'chart' : undefined}
  class={cn(
    'PortalTarget flex aspect-video justify-center text-xs focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-hidden',
    '[&_.lc-bar]:stroke-transparent [&_.lc-line]:stroke-border/50 [&_.lc-axis-tick]:stroke-0 [&_.lc-rule-y-line:not(.lc-grid-y-rule)]:stroke-0',
    '[&_.lc-highlight-point]:stroke-transparent [&_.lc-highlight-line]:stroke-0',
    '[&_.lc-text]:text-xs [&_text]:stroke-transparent [&_.lc-axis-tick-label]:fill-muted-foreground',
    '[&_.lc-grid-y-radial-line]:stroke-border [&_.lc-grid-y-radial-circle]:stroke-border',
    classProp
  )}
  {...props}
  {@attach keyboard}
>
  <ChartStyle id={chartId} {config} />
  {@render children?.()}
</div>
src/lib/components/ui/chart/ChartLegend.svelte
<script lang="ts">
  import ChartLegendContent from './ChartLegendContent.svelte';
  import type { ChartLegendProps } from './types.js';

  let { children, ...props }: ChartLegendProps = $props();
</script>

{#if children}
  {@render children()}
{:else}
  <ChartLegendContent {...props} />
{/if}
src/lib/components/ui/chart/ChartLegendContent.svelte
<script lang="ts">
  import { getChartContext } from 'layerchart';
  import { cn } from 'cn';
  import { configColor, getChartContainer, type ChartRow } from './context.js';
  import type { ChartLegendContentProps } from './types.js';

  let { hideIcon = false, nameKey, verticalAlign = 'bottom', class: classProp }: ChartLegendContentProps = $props();

  const chart = getChartContext<ChartRow>();
  const { config } = getChartContainer();

  const items = $derived(
    nameKey
      ? chart.flatData.map((row) => String(row[nameKey])).map((key) => ({ key, label: config[key]?.label, icon: config[key]?.icon, color: configColor(config, key) ?? chart.cScale?.(key) }))
      : chart.series.series.map((series) => ({
          key: series.key,
          label: config[series.key]?.label ?? series.label,
          icon: config[series.key]?.icon,
          color: configColor(config, series.key) ?? series.color
        }))
  );
</script>

<div class={cn('absolute inset-x-0 flex items-center justify-center gap-4', verticalAlign === 'top' ? 'top-0 pb-3' : 'bottom-0 pt-3', classProp)}>
  {#each items as item (item.key)}
    {const Icon = item.icon}
    <div class="flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground">
      {#if Icon && !hideIcon}
        <Icon />
      {:else}
        <div class="h-2 w-2 shrink-0 rounded-[2px]" style:background-color={item.color}></div>
      {/if}
      {item.label}
    </div>
  {/each}
</div>
src/lib/components/ui/chart/ChartStyle.svelte
<script lang="ts">
  import type { ChartConfig } from './context.js';

  let { id, config }: { id: string; config: ChartConfig } = $props();

  // Format: { THEME_NAME: CSS_SELECTOR }
  const THEMES = { light: '', dark: '.dark' } as const;

  const colorConfig = $derived(Object.entries(config).filter(([, item]) => item.theme ?? item.color));
  const css = $derived(
    Object.entries(THEMES)
      .map(
        ([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
  .map(([key, item]) => {
    const color = item.theme?.[theme as keyof typeof THEMES] ?? item.color;
    return color ? `  --color-${key}: ${color};` : null;
  })
  .join('\n')}
}
`
      )
      .join('\n')
  );
</script>

{#if colorConfig.length}
  <svelte:element this={'style'}>{css}</svelte:element>
{/if}
src/lib/components/ui/chart/ChartTooltip.svelte
<script lang="ts">
  import { getChartContext, Tooltip } from 'layerchart';
  import ChartTooltipContent from './ChartTooltipContent.svelte';
  import { getChartContainer, type ChartRow } from './context.js';
  import type { ChartTooltipProps } from './types.js';

  let { children: childrenProp, ...props }: ChartTooltipProps = $props();

  const chart = getChartContext<ChartRow>();
  const container = getChartContainer();
  $effect(() => container.registerTooltip(chart));
</script>

<Tooltip.Root variant="none">
  {#snippet children({ data })}
    {#if childrenProp}
      {@render childrenProp({ data })}
    {:else}
      <ChartTooltipContent {...props} />
    {/if}
  {/snippet}
</Tooltip.Root>
src/lib/components/ui/chart/ChartTooltipContent.svelte
<script lang="ts">
  import { getChartContext } from 'layerchart';
  import { cn } from 'cn';
  import { configColor, configKey, getChartContainer, type ChartRow } from './context.js';
  import type { ChartTooltipContentProps } from './types.js';

  let {
    hideLabel = false,
    hideIndicator = false,
    indicator = 'dot',
    nameKey,
    labelKey,
    label,
    labelFormatter,
    formatter,
    color,
    labelClass,
    class: classProp
  }: ChartTooltipContentProps = $props();

  const chart = getChartContext<ChartRow>();
  const { config } = getChartContainer();

  const data = $derived(chart.tooltip.data);
  const items = $derived(chart.tooltip.series.filter((series) => series.visible && series.value != null));
  const nestLabel = $derived(items.length === 1 && indicator !== 'dot');

  const header = $derived.by(() => {
    if (hideLabel || data == null) return;
    const category = label ?? (chart.valueAxis === 'x' ? chart.y : chart.x)?.(data);
    // Recharts hands upstream a string category; LayerChart's can be a Date or a number, so any category takes the string path.
    const text = !labelKey && category != null
      ? config[String(category)]?.label || category
      : config[configKey(config, data, labelKey ?? items[0]?.key ?? 'value')]?.label;
    if (labelFormatter) return labelFormatter(text, items);
    if (text) return text;
  });

  const rows = $derived(
    items.map((series) => {
      const key = configKey(config, data, nameKey ?? series.key);
      return {
        series,
        name: config[key]?.label ?? series.label,
        icon: config[key]?.icon,
        color: color ?? configColor(config, key) ?? series.color
      };
    })
  );
</script>

{#snippet headerRow()}
  {#if header !== undefined}
    <div class={cn('font-medium', labelClass)}>{header}</div>
  {/if}
{/snippet}

<div class={cn('border-border/50 bg-background gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl grid min-w-32 items-start', classProp)}>
  {#if !nestLabel}
    {@render headerRow()}
  {/if}
  <div class="grid gap-1.5">
    {#each rows as row, index (row.series.key)}
      {const Icon = row.icon}
      <div class={cn('flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground', indicator === 'dot' && 'items-center')}>
        {#if formatter && row.series.value !== undefined && row.series.label}
          {@render formatter({ item: row.series, data, index, name: row.name, value: row.series.value })}
        {:else}
          {#if Icon}
            <Icon />
          {:else if !hideIndicator}
            <div
              class={cn('shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)', {
                'h-2.5 w-2.5': indicator === 'dot',
                'w-1': indicator === 'line',
                'w-0 border-[1.5px] border-dashed bg-transparent': indicator === 'dashed',
                'my-0.5': nestLabel && indicator === 'dashed'
              })}
              style:--color-bg={row.color}
              style:--color-border={row.color}
            ></div>
          {/if}
          <div class={cn('flex flex-1 justify-between leading-none', nestLabel ? 'items-end' : 'items-center')}>
            <div class="grid gap-1.5">
              {#if nestLabel}
                {@render headerRow()}
              {/if}
              <span class="text-muted-foreground">{row.name}</span>
            </div>
            {#if row.series.value != null}
              <span class="font-mono font-medium text-foreground tabular-nums">
                {typeof row.series.value === 'number' ? row.series.value.toLocaleString() : String(row.series.value)}
              </span>
            {/if}
          </div>
        {/if}
      </div>
    {/each}
  </div>
</div>
src/lib/components/ui/chart/context.ts
import { createContext, type Component } from 'svelte';
import type { ChartState } from 'layerchart';

export type ChartConfig = Record<
  string,
  { label?: string; icon?: Component } & (
    | { color?: string; theme?: never }
    | { color?: never; theme: { light: string; dark: string } }
  )
>;

export type ChartRow = Record<string, unknown>;

export type ChartContainerContext = {
  readonly config: ChartConfig;
  /** Called by `ChartTooltip` so the container can drive its tooltip from the keyboard. Returns the cleanup. */
  registerTooltip(chart: ChartState<ChartRow>): () => void;
};

export const [getChartContainer, setChartContainer] = createContext<ChartContainerContext>();

/** The config key for a series or data row. A row field named by `key` wins when it holds a config key (`nameKey="browser"` on `{ browser: 'chrome' }`). */
export function configKey(config: ChartConfig, row: ChartRow | null, key: string): string {
  const value = row?.[key];
  return typeof value === 'string' && value in config ? value : key;
}

/** The CSS color for a config key: the per-chart variable when the config defines a color, otherwise nothing. */
export function configColor(config: ChartConfig, key: string): string | undefined {
  const entry = config[key];
  return entry?.color || entry?.theme ? `var(--color-${key})` : undefined;
}
src/lib/components/ui/chart/types.ts
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import type { TooltipSeries } from 'layerchart';

import type { ChartConfig, ChartRow } from './context.js';

export type { ChartConfig };

export type ChartContainerProps = HTMLAttributes<HTMLDivElement> & {
  config: ChartConfig;
};

export type ChartTooltipFormatterArgs = {
  /** The tooltip row: series key, value, color and visibility. */
  item: TooltipSeries;
  /** The hovered data row. */
  data: ChartRow;
  index: number;
  /** The config label, then the series label. */
  name: string;
  value: unknown;
};

export type ChartTooltipContentProps = Pick<HTMLAttributes<HTMLDivElement>, 'class'> & {
  hideLabel?: boolean;
  hideIndicator?: boolean;
  indicator?: 'dot' | 'line' | 'dashed';
  /** Data field holding the config key of each tooltip row, for charts whose series key is not the config key. */
  nameKey?: string;
  /** Config key (or data field holding one) whose label becomes the header. */
  labelKey?: string;
  /** Header text. A config key is replaced by its label. */
  label?: string;
  // Method syntax keeps the parameter bivariant, so `(value: Date) => string` is accepted for a date axis.
  labelFormatter?(value: unknown, items: TooltipSeries[]): string | number;
  /** Replaces a row's content. */
  formatter?: Snippet<[ChartTooltipFormatterArgs]>;
  /** Indicator color for every row. */
  color?: string;
  labelClass?: string;
};

export type ChartTooltipProps = ChartTooltipContentProps & {
  /** Replaces `ChartTooltipContent`. */
  children?: Snippet<[{ data: ChartRow }]>;
};

export type ChartLegendContentProps = Pick<HTMLAttributes<HTMLDivElement>, 'class'> & {
  hideIcon?: boolean;
  /** Data field naming each legend item, for charts whose items are rows (pie, radial) instead of series. */
  nameKey?: string;
  verticalAlign?: 'top' | 'bottom';
};

export type ChartLegendProps = ChartLegendContentProps & {
  children?: Snippet;
};
src/lib/components/ui/chart/index.ts
export { default as ChartContainer } from './ChartContainer.svelte';
export { default as ChartStyle } from './ChartStyle.svelte';
export { default as ChartTooltip } from './ChartTooltip.svelte';
export { default as ChartTooltipContent } from './ChartTooltipContent.svelte';
export { default as ChartLegend } from './ChartLegend.svelte';
export { default as ChartLegendContent } from './ChartLegendContent.svelte';
export type {
  ChartConfig,
  ChartContainerProps,
  ChartLegendContentProps,
  ChartLegendProps,
  ChartTooltipContentProps,
  ChartTooltipFormatterArgs,
  ChartTooltipProps
} from './types.js';

Add the colors to your CSS file

src/app.css
@layer base {
  :root {
    --chart-1: oklch(0.646 0.222 41.116);
    --chart-2: oklch(0.6 0.118 184.704);
    --chart-3: oklch(0.398 0.07 227.392);
    --chart-4: oklch(0.828 0.189 84.429);
    --chart-5: oklch(0.769 0.188 70.08);
  }

  .dark {
    --chart-1: oklch(0.488 0.243 264.376);
    --chart-2: oklch(0.696 0.17 162.48);
    --chart-3: oklch(0.769 0.188 70.08);
    --chart-4: oklch(0.627 0.265 303.9);
    --chart-5: oklch(0.645 0.246 16.439);
  }
}

Update the import paths

Change the imports to match your project setup.

Your First Chart

Let's build your first chart. We'll build a bar chart, then add a grid, an axis, a tooltip, and a legend.

Define your data

The following data holds the number of desktop and mobile users for each month.

src/lib/components/example-chart.svelte
<script lang="ts">
  const chartData = [
    { month: 'January', desktop: 186, mobile: 80 },
    { month: 'February', desktop: 305, mobile: 200 },
    { month: 'March', desktop: 237, mobile: 120 },
    { month: 'April', desktop: 73, mobile: 190 },
    { month: 'May', desktop: 209, mobile: 130 },
    { month: 'June', desktop: 214, mobile: 140 }
  ];
</script>

Define your chart config

The chart config holds the human-readable strings of a chart, such as labels, icons, and color tokens for theming.

src/lib/components/example-chart.svelte
<script lang="ts">
  import type { ChartConfig } from '$lib/components/ui/chart';

  const chartConfig = {
    desktop: { label: 'Desktop', color: '#2563eb' },
    mobile: { label: 'Mobile', color: '#60a5fa' }
  } satisfies ChartConfig;
</script>

Build your chart

Build the chart with a LayerChart component inside a ChartContainer. Pass each series the color var(--color-KEY), which ChartContainer defines from your config.

src/lib/components/example-chart.svelte
<ChartContainer config={chartConfig} class="h-[200px] w-full" aria-label="Desktop and mobile visitors by month">
  <BarChart
    data={chartData}
    x="month"
    seriesLayout="group"
    series={[
      { key: 'desktop', color: 'var(--color-desktop)' },
      { key: 'mobile', color: 'var(--color-mobile)' }
    ]}
    axis={false}
    grid={false}
    rule={false}
    tooltipContext={false}
  />
</ChartContainer>

Add a Grid

A BarChart draws a grid by default. Remove grid={false} to show it.

<ChartContainer config={chartConfig} class="h-[200px] w-full" aria-label="Desktop and mobile visitors by month">
  <BarChart
    data={chartData}
    x="month"
    seriesLayout="group"
    series={[ /* ... */ ]}
    axis={false}
    rule={false}
    tooltipContext={false}
  />
</ChartContainer>

Add an Axis

To add an x-axis, set axis="x". Format its labels with props.xAxis.format.

<ChartContainer config={chartConfig} class="h-[200px] w-full" aria-label="Desktop and mobile visitors by month">
  <BarChart
    data={chartData}
    x="month"
    seriesLayout="group"
    series={[ /* ... */ ]}
    axis="x"
    props={{ xAxis: { format: (value: string) => value.slice(0, 3) } }}
    rule={false}
    tooltipContext={false}
  />
</ChartContainer>

Add a Tooltip

So far we have only used LayerChart. The charts look right because ChartContainer styles LayerChart for you.

To add a tooltip, render the ChartTooltip component in the tooltip snippet of the chart, and remove tooltipContext={false}.

<script lang="ts">
  import { ChartContainer, ChartTooltip } from '$lib/components/ui/chart';
</script>

<ChartContainer config={chartConfig} class="h-[200px] w-full" aria-label="Desktop and mobile visitors by month">
  <BarChart
    data={chartData}
    x="month"
    seriesLayout="group"
    series={[ /* ... */ ]}
    axis="x"
    props={{ xAxis: { format: (value: string) => value.slice(0, 3) } }}
    rule={false}
  >
    {#snippet tooltip()}
      <ChartTooltip />
    {/snippet}
  </BarChart>
</ChartContainer>

Hover the chart to see the tooltip. Two components, and you have a tooltip that reads your config.

Add a Legend

Do the same for the legend. Render the ChartLegend component in the legend snippet of the chart.

<script lang="ts">
  import { ChartContainer, ChartLegend, ChartTooltip } from '$lib/components/ui/chart';
</script>

<ChartContainer config={chartConfig} class="h-[200px] w-full" aria-label="Desktop and mobile visitors by month">
  <BarChart
    data={chartData}
    x="month"
    seriesLayout="group"
    series={[ /* ... */ ]}
    axis="x"
    props={{ xAxis: { format: (value: string) => value.slice(0, 3) } }}
    rule={false}
  >
    {#snippet tooltip()}
      <ChartTooltip />
    {/snippet}
    {#snippet legend()}
      <ChartLegend />
    {/snippet}
  </BarChart>
</ChartContainer>

Done. You have built your first chart. What's next?

Chart Config

The chart config is where you define the labels, icons, and colors of a chart.

It is intentionally decoupled from chart data. You can share a config and its color tokens between charts. It also works when your data or color tokens live somewhere else, or in another format.

src/lib/components/example-chart.svelte
<script lang="ts">
  import MonitorIcon from '@lucide/svelte/icons/monitor';
  import type { ChartConfig } from '$lib/components/ui/chart';

  const chartConfig = {
    desktop: {
      label: 'Desktop',
      icon: MonitorIcon,
      // A color like 'hsl(220, 98%, 61%)' or 'var(--color-name)'
      color: '#2563eb'
    },
    mobile: {
      label: 'Mobile',
      // Or a theme object with 'light' and 'dark' keys
      theme: { light: '#2563eb', dark: '#dc2626' }
    }
  } satisfies ChartConfig;
</script>

An entry takes a color or a theme, never both. The icon is any Svelte component that takes no props. The tooltip and the legend show it in place of the color swatch.

In the examples on this page, each config icon is a small component that renders an IconPlaceholder, so the icon follows the icon library you pick. Use the icon component of your own project instead.

Theming

Charts have built-in support for theming. Use CSS variables, which we recommend, or color values in any format, such as hex, hsl, or oklch.

CSS variables

Define your colors in your CSS file

src/app.css
@layer base {
  :root {
    --chart-1: oklch(0.646 0.222 41.116);
    --chart-2: oklch(0.6 0.118 184.704);
  }

  .dark {
    --chart-1: oklch(0.488 0.243 264.376);
    --chart-2: oklch(0.696 0.17 162.48);
  }
}

Add the colors to your chart config

src/lib/components/example-chart.svelte
const chartConfig = {
  desktop: { label: 'Desktop', color: 'var(--chart-1)' },
  mobile: { label: 'Mobile', color: 'var(--chart-2)' }
} satisfies ChartConfig;

hex, hsl or oklch

You can also define your colors in the chart config. Use the color format you prefer.

src/lib/components/example-chart.svelte
const chartConfig = {
  desktop: { label: 'Desktop', color: '#2563eb' },
  mobile: { label: 'Mobile', color: 'hsl(220, 98%, 61%)' },
  tablet: { label: 'Tablet', color: 'oklch(0.5 0.2 240)' },
  laptop: { label: 'Laptop', color: 'var(--chart-2)' }
} satisfies ChartConfig;

Light and dark colors

Use theme to give a key one color for each mode. The .dark class selects the dark color.

const chartConfig = {
  desktop: { label: 'Desktop', theme: { light: '#2563eb', dark: '#60a5fa' } }
} satisfies ChartConfig;

Using colors

ChartContainer writes a --color-KEY variable for each config key and scopes it to the chart. Reference the colors with var(--color-KEY).

Series

<BarChart series={[{ key: 'desktop', color: 'var(--color-desktop)' }]} />

Chart data

Rows that carry their own color, such as the slices of a pie, name the variable in the data.

src/lib/components/example-chart.svelte
const chartData = [
  { browser: 'chrome', visitors: 275, color: 'var(--color-chrome)' },
  { browser: 'safari', visitors: 200, color: 'var(--color-safari)' }
];

Tailwind

<Text class="fill-(--color-desktop)" />

Tooltip

A chart tooltip contains a label, a name, an indicator, and a value. You can combine these to customize the tooltip.

Turn off the label or the indicator with hideLabel and hideIndicator. Change the indicator style with indicator. Use labelKey and nameKey to read the label and the name from another key.

ChartTooltip replaces the default tooltip of a LayerChart chart. Render it in the tooltip snippet.

src/lib/components/example-chart.svelte
<script lang="ts">
  import { ChartTooltip } from '$lib/components/ui/chart';
</script>

<BarChart {data} x="month" series={series}>
  {#snippet tooltip()}
    <ChartTooltip indicator="dashed" />
  {/snippet}
</BarChart>

Hover the charts in the tooltip examples to compare the options.

Props

ChartTooltip takes the following props, and the same props go to ChartTooltipContent.

PropTypeDefaultDescription
hideLabelbooleanfalseHides the label
hideIndicatorbooleanfalseHides the indicator
indicator'dot' \| 'line' \| 'dashed''dot'The indicator style
labelstringThe label text. A config key is replaced by its label
labelKeystringA config key, or a data field that holds one, whose label becomes the label
nameKeystringThe data field that holds the config key of each row, for charts whose series key is not the config key
labelFormatter(value, items) => string \| numberFormats the label
formatterSnippet<[{ item, data, index, name, value }]>Replaces the content of a row. data is the hovered data row
colorstringThe indicator color of every row
labelClassstringAdded to the label
classstringAdded to the tooltip
childrenSnippet<[{ data }]>Replaces the whole tooltip

Colors

Colors come from the chart config. The indicator uses the color of the config key of each row.

Custom

A pie or a radial chart has one series, and each slice is a row. Use nameKey to name the data field that holds the config key of the slice.

const chartData = [
  { browser: 'chrome', visitors: 187, color: 'var(--color-chrome)' },
  { browser: 'safari', visitors: 200, color: 'var(--color-safari)' }
];

const chartConfig = {
  visitors: { label: 'Total Visitors' },
  chrome: { label: 'Chrome', color: 'var(--chart-1)' },
  safari: { label: 'Safari', color: 'var(--chart-2)' }
} satisfies ChartConfig;
src/lib/components/example-chart.svelte
<ChartTooltip hideLabel nameKey="browser" />

The tooltip names the rows Chrome and Safari. Add labelKey="visitors" to use Total Visitors as the label.

Legend

Render the ChartLegend component in the legend snippet of a chart to add a legend. The snippet also reserves room below the plot in area, bar, and line charts.

src/lib/components/example-chart.svelte
<script lang="ts">
  import { ChartLegend } from '$lib/components/ui/chart';
</script>

<BarChart {data} x="month" series={series}>
  {#snippet legend()}
    <ChartLegend />
  {/snippet}
</BarChart>

Use the legend snippet instead of the legend prop of LayerChart. Svelte reports an error when a component gets both.

Pie, radial, and radar charts do not reserve room for the snippet. Pass padding to the chart, for example padding={{ bottom: 32 }}, to keep the legend off the plot.

Props

PropTypeDefaultDescription
hideIconbooleanfalseShows a color swatch even when the config has an icon
nameKeystringThe data field that names each item, for charts whose items are rows (pie, radial) instead of series
verticalAlign'top' \| 'bottom''bottom'The side of the chart that holds the legend
classstringAdded to the legend
childrenSnippetReplaces the legend

Colors

Colors come from the chart config, in the order of the series.

Custom

Use nameKey to name the data field that holds the config key of each item.

const chartData = [
  { browser: 'chrome', visitors: 187, color: 'var(--color-chrome)' },
  { browser: 'safari', visitors: 200, color: 'var(--color-safari)' }
];

const chartConfig = {
  chrome: { label: 'Chrome', color: 'var(--chart-1)' },
  safari: { label: 'Safari', color: 'var(--chart-2)' }
} satisfies ChartConfig;
src/lib/components/example-chart.svelte
<ChartLegend nameKey="browser" />

The legend names the items Chrome and Safari.

Accessibility

A LayerChart chart is an SVG, and the marks of the chart have no text alternative. Describe what the chart shows with aria-label on the ChartContainer, and give a table or text version when readers need the exact values.

A chart that has a ChartTooltip is reachable with the keyboard. The container becomes a focusable element with role="application" and aria-roledescription="chart", and it steps through the rows of the chart. The tooltip hides when the chart loses focus.

KeyAction
Arrow Right, Arrow DownShow the tooltip of the next row
Arrow Left, Arrow UpShow the tooltip of the previous row
Home, EndShow the tooltip of the first or the last row
EscapeHide the tooltip

API reference

ChartContainer

PropTypeDefaultDescription
configChartConfigRequired. The label, icon, and color or theme of each key
classstringAdded to the container. It is aspect-video by default

ChartContainer passes every other attribute to its div. Give it an aria-label.

ChartTooltip and ChartLegend

See the props of the tooltip and of the legend.

ChartConfig

FieldTypeDescription
labelstringThe name shown in the tooltip and the legend
iconComponentShown in place of the color swatch
colorstringAny CSS color. Not allowed with theme
theme{ light: string; dark: string }A color for each mode. Not allowed with color

Examples

Area Chart
Area Chart
Showing total visitors for the last 6 months
Bar Chart
Bar Chart - Multiple
January - June 2024
Line Chart
Line Chart - Multiple
January - June 2024
Pie Chart
Pie Chart - Donut with Text
January - June 2024
Radial Chart
Radial Chart - Shape
January - June 2024
Radar Chart
Radar Chart - Multiple
Showing total visitors for the last 6 months

Area

Area Chart

Area Chart
Showing total visitors for the last 6 months

Area Chart - Linear

Area Chart - Linear
Showing total visitors for the last 6 months

Area Chart - Step

Area Chart - Step
Showing total visitors for the last 6 months

Area Chart - Legend

Area Chart - Legend
Showing total visitors for the last 6 months

Area Chart - Stacked

Area Chart - Stacked
Showing total visitors for the last 6 months

Area Chart - Stacked Expanded

Area Chart - Stacked Expanded
Showing total visitors for the last 6 months

Area Chart - Icons

Area Chart - Icons
Showing total visitors for the last 6 months

Area Chart - Gradient

Area Chart - Gradient
Showing total visitors for the last 6 months

Area Chart - Axes

Area Chart - Axes
Showing total visitors for the last 6 months

Area Chart - Interactive

Area Chart - Interactive
Showing total visitors for the last 3 months

Bar

Bar Chart

Bar Chart
January - June 2024

Bar Chart - Horizontal

Bar Chart - Horizontal
January - June 2024

Bar Chart - Multiple

Bar Chart - Multiple
January - June 2024

Bar Chart - Label

Bar Chart - Label
January - June 2024

Bar Chart - Custom Label

Bar Chart - Custom Label
January - June 2024

Bar Chart - Mixed

Bar Chart - Mixed
January - June 2024

Bar Chart - Active

Bar Chart - Active
January - June 2024

Bar Chart - Stacked + Legend

Bar Chart - Stacked + Legend
January - June 2024

Bar Chart - Negative

Bar Chart - Negative
January - June 2024

Bar Chart - Interactive

Bar Chart - Interactive
Showing total visitors for the last 3 months

Line

Line Chart

Line Chart
Showing total visitors for the last 6 months

Line Chart - Linear

Line Chart - Linear
Showing total visitors for the last 6 months

Line Chart - Step

Line Chart - Step
Showing total visitors for the last 6 months

Line Chart - Multiple

Line Chart - Multiple
Showing total visitors for the last 6 months

Line Chart - Dots

Line Chart - Dots
Showing total visitors for the last 6 months

Line Chart - Dots Custom

Line Chart - Dots Custom
Showing total visitors for the last 6 months

Line Chart - Dots Colors

Line Chart - Dots Colors
Showing total visitors for the last 6 months

Line Chart - Label

Line Chart - Label
Showing total visitors for the last 6 months

Line Chart - Custom Label

Line Chart - Custom Label
Showing total visitors for the last 6 months

Line Chart - Interactive

Line Chart - Interactive
Showing total visitors for the last 3 months

Pie

Pie Chart

Pie Chart
January - June 2024

Pie Chart - Separator None

Pie Chart - Separator None
January - June 2024

Pie Chart - Label

Pie Chart - Label
January - June 2024

Pie Chart - Custom Label

Pie Chart - Custom Label
January - June 2024

Pie Chart - Label List

Pie Chart - Label List
January - June 2024

Pie Chart - Legend

Pie Chart - Legend
January - June 2024

Pie Chart - Donut

Pie Chart - Donut
January - June 2024

Pie Chart - Donut Active

Pie Chart - Donut Active
January - June 2024

Pie Chart - Donut with Text

Pie Chart - Donut with Text
January - June 2024

Pie Chart - Stacked

Pie Chart - Stacked
January - June 2024

Pie Chart - Interactive

Pie Chart - Interactive
January - June 2024

Radar

Radar Chart

Radar Chart
Showing total visitors for the last 6 months

Radar Chart - Dots

Radar Chart - Dots
Showing total visitors for the last 6 months

Radar Chart - Lines Only

Radar Chart - Lines Only
Showing total visitors for the last 6 months

Radar Chart - Custom Label

Radar Chart - Custom Label
Showing total visitors for the last 6 months

Radar Chart - Radius Axis

Radar Chart - Radius Axis
Showing total visitors for the last 6 months

Radar Chart - Grid Custom

Radar Chart - Grid Custom
Showing total visitors for the last 6 months

Radar Chart - Grid Circle

Radar Chart - Grid Circle
Showing total visitors for the last 6 months

Radar Chart - Grid Circle - No Lines

Radar Chart - Grid Circle - No Lines
Showing total visitors for the last 6 months

Radar Chart - Grid Circle Filled

Radar Chart - Grid Circle Filled
Showing total visitors for the last 6 months

Radar Chart - Grid Filled

Radar Chart - Grid Filled
Showing total visitors for the last 6 months

Radar Chart - Grid None

Radar Chart - Grid None
Showing total visitors for the last 6 months

Radar Chart - Icons

Radar Chart - Icons
Showing total visitors for the last 6 months

Radar Chart - Legend

Radar Chart - Legend
Showing total visitors for the last 6 months

Radar Chart - Multiple

Radar Chart - Multiple
Showing total visitors for the last 6 months

Radial

Radial Chart

Radial Chart
Showing total visitors for the last 6 months

Radial Chart - Label

Radial Chart - Label
Showing total visitors for the last 6 months

Radial Chart - Grid

Radial Chart - Grid
Showing total visitors for the last 6 months

Radial Chart - Text

Radial Chart - Text
Showing total visitors for the last 6 months

Radial Chart - Shape

Radial Chart - Shape
Showing total visitors for the last 6 months

Radial Chart - Stacked

Radial Chart - Stacked
January - June 2024

Tooltip examples

Tooltip - Default

Tooltip - Default
Default tooltip with ChartTooltip.

Tooltip - Line Indicator

Tooltip - Line Indicator
Tooltip with line indicator.

Tooltip - No Indicator

Tooltip - No Indicator
Tooltip with no indicator.

Tooltip - Custom Label

Tooltip - Custom Label
Tooltip with custom label from chartConfig.

Tooltip - Label Formatter

Tooltip - Label Formatter
Tooltip with label formatter.

Tooltip - No Label

Tooltip - No Label
Tooltip with no label.

Tooltip - Formatter

Tooltip - Formatter
Tooltip with custom formatter.

Tooltip - Icons

Tooltip - Icons
Tooltip with icons.

Tooltip - Advanced

Tooltip - Advanced
Tooltip with custom formatter and total.