Sabil UI

Search documentation

Search for a command to run...

Components

Chart

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

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

Installation#

  1. Install the dependencies

    npm install cn recharts
  2. Copy the source into your project

    components/ui/chart.tsx
    "use client"import * as React from "react"import { cn } from "cn"import * as RechartsPrimitive from "recharts"import type { TooltipValueType } from "recharts"// Format: { THEME_NAME: CSS_SELECTOR }const THEMES = { light: "", dark: ".dark" } as constconst INITIAL_DIMENSION = { width: 320, height: 200 } as consttype TooltipNameType = number | stringexport type ChartConfig = Record<  string,  {    label?: React.ReactNode    icon?: React.ComponentType  } & (    | { color?: string; theme?: never }    | { color?: never; theme: Record<keyof typeof THEMES, string> }  )>type ChartContextProps = {  config: ChartConfig}const ChartContext = React.createContext<ChartContextProps | null>(null)function useChart() {  const context = React.useContext(ChartContext)  if (!context) {    throw new Error("useChart must be used within a <ChartContainer />")  }  return context}function ChartContainer({  id,  className,  children,  config,  initialDimension = INITIAL_DIMENSION,  ...props}: React.ComponentProps<"div"> & {  config: ChartConfig  children: React.ComponentProps<    typeof RechartsPrimitive.ResponsiveContainer  >["children"]  initialDimension?: {    width: number    height: number  }}) {  const uniqueId = React.useId()  const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`  return (    <ChartContext.Provider value={{ config }}>      <div        data-slot="chart"        data-chart={chartId}        className={cn(          "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",          className        )}        {...props}      >        <ChartStyle id={chartId} config={config} />        <RechartsPrimitive.ResponsiveContainer          initialDimension={initialDimension}        >          {children}        </RechartsPrimitive.ResponsiveContainer>      </div>    </ChartContext.Provider>  )}const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {  const colorConfig = Object.entries(config).filter(    ([, config]) => config.theme ?? config.color  )  if (!colorConfig.length) {    return null  }  return (    <style      dangerouslySetInnerHTML={{        __html: Object.entries(THEMES)          .map(            ([theme, prefix]) => `${prefix} [data-chart=${id}] {${colorConfig  .map(([key, itemConfig]) => {    const color =      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??      itemConfig.color    return color ? `  --color-${key}: ${color};` : null  })  .join("\n")}}`          )          .join("\n"),      }}    />  )}const ChartTooltip = RechartsPrimitive.Tooltipfunction ChartTooltipContent({  active,  payload,  className,  indicator = "dot",  hideLabel = false,  hideIndicator = false,  label,  labelFormatter,  labelClassName,  formatter,  color,  nameKey,  labelKey,}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &  React.ComponentProps<"div"> & {    hideLabel?: boolean    hideIndicator?: boolean    indicator?: "line" | "dot" | "dashed"    nameKey?: string    labelKey?: string  } & Omit<    RechartsPrimitive.DefaultTooltipContentProps<      TooltipValueType,      TooltipNameType    >,    "accessibilityLayer"  >) {  const { config } = useChart()  const tooltipLabel = React.useMemo(() => {    if (hideLabel || !payload?.length) {      return null    }    const [item] = payload    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`    const itemConfig = getPayloadConfigFromPayload(config, item, key)    const value =      !labelKey && typeof label === "string"        ? (config[label]?.label ?? label)        : itemConfig?.label    if (labelFormatter) {      return (        <div className={cn("font-medium", labelClassName)}>          {labelFormatter(value, payload)}        </div>      )    }    if (!value) {      return null    }    return <div className={cn("font-medium", labelClassName)}>{value}</div>  }, [    label,    labelFormatter,    payload,    hideLabel,    labelClassName,    config,    labelKey,  ])  if (!active || !payload?.length) {    return null  }  const nestLabel = payload.length === 1 && indicator !== "dot"  return (    <div      className={cn(        "grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",        className      )}    >      {!nestLabel ? tooltipLabel : null}      <div className="grid gap-1.5">        {payload          .filter((item) => item.type !== "none")          .map((item, index) => {            const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`            const itemConfig = getPayloadConfigFromPayload(config, item, key)            const indicatorColor = color ?? item.payload?.fill ?? item.color            return (              <div                key={index}                className={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"                )}              >                {formatter && item?.value !== undefined && item.name ? (                  formatter(item.value, item.name, item, index, item.payload)                ) : (                  <>                    {itemConfig?.icon ? (                      <itemConfig.icon />                    ) : (                      !hideIndicator && (                        <div                          className={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": indicatorColor,                              "--color-border": indicatorColor,                            } as React.CSSProperties                          }                        />                      )                    )}                    <div                      className={cn(                        "flex flex-1 justify-between leading-none",                        nestLabel ? "items-end" : "items-center"                      )}                    >                      <div className="grid gap-1.5">                        {nestLabel ? tooltipLabel : null}                        <span className="text-muted-foreground">                          {itemConfig?.label ?? item.name}                        </span>                      </div>                      {item.value != null && (                        <span className="font-mono font-medium text-foreground tabular-nums">                          {typeof item.value === "number"                            ? item.value.toLocaleString("en-US")                            : String(item.value)}                        </span>                      )}                    </div>                  </>                )}              </div>            )          })}      </div>    </div>  )}const ChartLegend = RechartsPrimitive.Legendfunction ChartLegendContent({  className,  hideIcon = false,  payload,  verticalAlign = "bottom",  nameKey,}: React.ComponentProps<"div"> & {  hideIcon?: boolean  nameKey?: string} & RechartsPrimitive.DefaultLegendContentProps) {  const { config } = useChart()  if (!payload?.length) {    return null  }  return (    <div      className={cn(        "flex items-center justify-center gap-4",        verticalAlign === "top" ? "pb-3" : "pt-3",        className      )}    >      {payload        .filter((item) => item.type !== "none")        .map((item, index) => {          const key = `${nameKey ?? item.dataKey ?? "value"}`          const itemConfig = getPayloadConfigFromPayload(config, item, key)          return (            <div              key={index}              className={cn(                "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"              )}            >              {itemConfig?.icon && !hideIcon ? (                <itemConfig.icon />              ) : (                <div                  className="h-2 w-2 shrink-0 rounded-[2px]"                  style={{                    backgroundColor: item.color,                  }}                />              )}              {itemConfig?.label}            </div>          )        })}    </div>  )}function getPayloadConfigFromPayload(  config: ChartConfig,  payload: unknown,  key: string) {  if (typeof payload !== "object" || payload === null) {    return undefined  }  const payloadPayload =    "payload" in payload &&    typeof payload.payload === "object" &&    payload.payload !== null      ? payload.payload      : undefined  let configLabelKey: string = key  if (    key in payload &&    typeof payload[key as keyof typeof payload] === "string"  ) {    configLabelKey = payload[key as keyof typeof payload] as string  } else if (    payloadPayload &&    key in payloadPayload &&    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"  ) {    configLabelKey = payloadPayload[      key as keyof typeof payloadPayload    ] as string  }  return configLabelKey in config ? config[configLabelKey] : config[key]}export {  ChartContainer,  ChartTooltip,  ChartTooltipContent,  ChartLegend,  ChartLegendContent,  ChartStyle,}
  3. Make sure the foundation is in place

    Components read their colors, radius and surfaces from the tokens in app/globals.css. See Theming.

Usage#

import {  ChartContainer,  ChartTooltip,  ChartTooltipContent,  ChartLegend,  ChartLegendContent,  ChartStyle,} from "@/components/ui/chart"

Examples#

Example

Example Axis

Example Grid

Example Legend

Example Tooltip

Tooltip

Label
Page Views
Desktop
186
Mobile
80
Name
Chrome
1,286
Firefox
1,000
Page Views
Desktop
12,486
Indicator
Chrome
1,286