Chart:HextaUI と shadcn/ui

HextaUI の Chart は shadcn/ui の名前とテーマトークンをそのまま使い、同じ CLI でインストールできます。このページでは、両方のソースから読み取った違いをまとめています。shadcn/ui 側は 2026-10-16 時点の base-vega レジストリです。

早見表

項目HextaUIshadcn/ui
エクスポートされるパーツ63
ラッパーが処理する props2619
ソースの行数516372
npm の依存関係cnrecharts@^3.10.1cn[email protected]
構成要素themecard

ソースが多いこと自体が良いわけではありません。HextaUI の追加の行の多くは、本来なら自分で書くことになる状態、モーション、エッジケースを扱っています。

パーツ

HextaUI だけChartStyleChartTooltipChartLegend

両方にあるChartContainerChartTooltipContentChartLegendContent

HextaUI が追加する props

HextaUI のラッパーが自分で処理し、shadcn/ui のラッパーは処理しない props です。Base UI や下位ライブラリにそのまま渡される props は、どちらのバージョンでも使えるため除いています。

プロパティ内容
valueFormatterChartTooltipContent各値を整形し、インジケーターと名前は維持します。
localeChartTooltipContent数値を整形します。

HextaUI 版

Visitors
Desktop and mobile visits per day
"use client"

import * as React from "react"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"

import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

const chartData = Array.from({ length: 90 }, (_, index) => {
  const date = new Date(Date.UTC(2026, 3, 1 + index))
  const wave = Math.sin(index / 6) * 60 + Math.sin(index / 2.3) * 25
  return {
    date: date.toISOString().slice(0, 10),
    desktop: Math.round(260 + wave + index * 1.6),
    mobile: Math.round(180 - wave * 0.6 + index * 1.1),
  }
})

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "var(--chart-1)",
  },
  mobile: {
    label: "Mobile",
    color: "var(--chart-2)",
  },
} satisfies ChartConfig

const ranges = [
  { value: "90", label: "90d", name: "Last 90 days" },
  { value: "30", label: "30d", name: "Last 30 days" },
  { value: "7", label: "7d", name: "Last 7 days" },
]

const dateFormat = new Intl.DateTimeFormat("en-US", {
  month: "short",
  day: "numeric",
  timeZone: "UTC",
})

function formatDate(value: unknown) {
  return dateFormat.format(new Date(`${value}T00:00:00Z`))
}

export function ChartDemo() {
  const [range, setRange] = React.useState("90")
  const data = chartData.slice(-Number(range))

  return (
    <Card className="w-full">
      <CardHeader>
        <CardTitle>Visitors</CardTitle>
        <CardDescription>Desktop and mobile visits per day</CardDescription>
        <CardAction>
          <ToggleGroup
            variant="outline"
            size="sm"
            aria-label="Time range"
            value={[range]}
            onValueChange={(value) => {
              if (value.length > 0) {
                setRange(value[0])
              }
            }}
          >
            {ranges.map((item) => (
              <ToggleGroupItem
                key={item.value}
                value={item.value}
                aria-label={item.name}
              >
                {item.label}
              </ToggleGroupItem>
            ))}
          </ToggleGroup>
        </CardAction>
      </CardHeader>
      <CardContent>
        <ChartContainer
          aria-label="Area chart of desktop and mobile visitors per day"
          config={chartConfig}
          className="h-64 w-full"
        >
          <AreaChart data={data} margin={{ left: 12, right: 12 }}>
            <defs>
              <linearGradient id="fill-desktop" x1="0" y1="0" x2="0" y2="1">
                <stop
                  offset="5%"
                  stopColor="var(--color-desktop)"
                  stopOpacity={0.3}
                />
                <stop
                  offset="95%"
                  stopColor="var(--color-desktop)"
                  stopOpacity={0.02}
                />
              </linearGradient>
              <linearGradient id="fill-mobile" x1="0" y1="0" x2="0" y2="1">
                <stop
                  offset="5%"
                  stopColor="var(--color-mobile)"
                  stopOpacity={0.3}
                />
                <stop
                  offset="95%"
                  stopColor="var(--color-mobile)"
                  stopOpacity={0.02}
                />
              </linearGradient>
            </defs>
            <CartesianGrid vertical={false} />
            <XAxis
              dataKey="date"
              tickLine={false}
              axisLine={false}
              tickMargin={8}
              minTickGap={32}
              tickFormatter={formatDate}
            />
            <ChartTooltip
              cursor={false}
              content={
                <ChartTooltipContent
                  indicator="dot"
                  labelFormatter={formatDate}
                />
              }
            />
            <Area
              dataKey="mobile"
              type="natural"
              fill="url(#fill-mobile)"
              stroke="var(--color-mobile)"
              strokeWidth={2}
              stackId="a"
            />
            <Area
              dataKey="desktop"
              type="natural"
              fill="url(#fill-desktop)"
              stroke="var(--color-desktop)"
              strokeWidth={2}
              stackId="a"
            />
            <ChartLegend content={<ChartLegendContent />} />
          </AreaChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}

すべての状態、キーボード操作、API リファレンスはコンポーネントのページにあります。

このうち 13 個の例には専用のページもあります。

コンポーネントを 1 つ切り替える

add コマンドを --overwrite 付きで実行して @/components/ui の shadcn/ui のファイルを置き換え、残す前に git で差分を確認してください。import はそのままで、置き換えないコンポーネントもそのまま動きます。

pnpm dlx shadcn@latest add https://hextaui.com/r/chart.json --overwrite
  • 上の表の props はすべて追加分なので、shadcn/ui 向けに書いた呼び出しではまだ渡されていません。
  • 切り替える前に、shadcn/ui にだけあるパーツやバリアントを確認し、最も近い HextaUI のものに置き換えてください。

ほかのコンポーネントの比較

その他の比較