Skip to content
Domphy

chartBarDefault

A Charts block/component from shadcn/ui — clean-room reimplemented for Domphy (see methodology). Call chartBarDefault() with no arguments for a working demo, or edit the code below live.

Props

PropTypeDescription
dataChartBarPoint[]
seriesLabelstring
seriesColorThemeColor
titlestring
subtitlestring
trendTextstring
trendDirectionChartTrendDirection
showTrendIconboolean
captionTextstring
cornerRadiusnumberBar corner radius, px. NOTE:
heightnumber
Implementation notes

Card + single-series vertical bar chart via @domphy/chart's chart() engine (packages/chart), with axis-trigger tooltip (formatter shows month+value), axisPointer:'shadow' for the hover column highlight, hidden y-axis with visible horizontal split lines, and a trend footer. Two approximations, both documented in chart-bar-shared.ts: (1) BarRenderer (packages/chart/src/gl/BarRenderer.ts) hardcodes a fixed 2px bar corner radius and never reads a bar's itemStyle.borderRadius, so the cornerRadius prop (default 8) is forwarded into the chart option for forward-compatibility but currently has no visible effect — bars always render with the engine's fixed 2px radius. (2) The engine has no per-bar 'grow from 0' height tween, so the spec's mount reveal is approximated with a clip-path wipe across the whole plot (via @domphy/ui's motion()) rather than a true per-bar tween. The hover-highlight rectangle is also a fixed 40px-wide shadow band (engine's axisPointer:'shadow' behavior), not sized to the exact bar width. Direct-source-diff fix (2026-07-05): Hover cursor rectangle was enabled — upstream disables it (cursor={false}) since the tooltip alone carries the hover feedback. Fixed.

Status: ported · Reference: shadcn/ui original

// shadcn/ui "chart-bar" (default recipe) — clean-room reimplementation.
//
// A single-series monthly bar chart in a card: one accent-colored bar per
// month with rounded top corners, floating hover tooltip + highlighted
// column, above a trend-sentence footer.
//
// Implemented purely from the block's public functional/visual spec — no
// upstream shadcn/ui source was viewed or copied.

import type { ChartOption, TooltipParams } from "@domphy/chart";
import type { DomphyElement } from "@domphy/core";
import {
  CHART_BAR_MONTHLY_DATA,
  type ChartBarPoint,
  type ChartTrendDirection,
  chartBarCardShell,
  chartBarCategoryXAxis,
  chartBarFrame,
  chartBarHiddenValueYAxis,
  chartBarSeriesColor,
  chartBarTooltipRow,
  chartBarTrendFooter,
  chartBarValueDomain,
} from "./chart-bar-shared.js";

export interface ChartBarDefaultProps {
  data?: ChartBarPoint[];
  seriesLabel?: string;
  /** Bar color — a theme role (resolved at shift-9) or literal ramp hex.
   * Defaults to the ramp's first step (upstream var(--chart-1) ≈ blue-300). */
  seriesColor?: string;
  title?: string;
  subtitle?: string;
  trendText?: string;
  trendDirection?: ChartTrendDirection;
  showTrendIcon?: boolean;
  captionText?: string;
  /** Bar corner radius, px. NOTE: @domphy/chart's BarRenderer hardcodes a
   * fixed 2px corner radius and never reads a bar's `itemStyle.borderRadius`
   * (verified against packages/chart/src/gl/BarRenderer.ts) — this prop is
   * forwarded to the chart option for forward-compatibility but currently
   * has no visible effect. */
  cornerRadius?: number;
  height?: number;
}

/**
 * shadcn/ui "chart-bar" default recipe — a single-series monthly bar chart
 * with a trend footer. Call with no arguments for a working demo.
 */
function chartBarDefault(
  props: ChartBarDefaultProps = {},
): DomphyElement<"div"> {
  const {
    data = CHART_BAR_MONTHLY_DATA,
    seriesLabel = "Desktop",
    seriesColor = chartBarSeriesColor(0).css,
    title = "Bar Chart",
    subtitle = "January - June 2026",
    trendText = "Trending up by 5.2% this month",
    trendDirection = "up",
    showTrendIcon = true,
    captionText = "Showing total visitors for the last 6 months",
    cornerRadius = 8,
    height = 64,
  } = props;

  const categories = data.map((point) => point.label);
  const values = data.map((point) => point.value);

  const option: ChartOption = {
    tooltip: {
      trigger: "axis",
      axisPointer: { type: "none" },
      // Upstream renders this recipe's tooltip with hideLabel — the month
      // name is dropped, only the series dot + name + value line shows.
      formatter: chartBarDefaultTooltipFormatter,
    },
    xAxis: chartBarCategoryXAxis(categories),
    yAxis: chartBarHiddenValueYAxis({ ...chartBarValueDomainOptions(values) }),
    // Bottom margin fits the full x-axis label row: the engine draws labels
    // 18px below the grid's bottom edge (11px hanging text), so 24px clipped
    // the glyphs at the frame edge.
    grid: { left: 8, right: 8, top: 16, bottom: 32 },
    series: [
      {
        type: "bar",
        name: seriesLabel,
        color: seriesColor,
        itemStyle: { borderRadius: [cornerRadius, cornerRadius, 0, 0] },
        data: values,
      },
    ],
  };

  return chartBarCardShell({
    title,
    subtitle,
    content: { div: [chartBarFrame(option, { height })] },
    footer: chartBarTrendFooter({
      trendText,
      direction: trendDirection,
      captionText,
      showIcon: showTrendIcon,
    }),
  });
}

function escapeTooltipHtml(text: string): string {
  return text
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
}

function chartBarDefaultTooltipFormatter(
  parametersInput: TooltipParams | TooltipParams[],
): string {
  const parameters = Array.isArray(parametersInput)
    ? parametersInput
    : [parametersInput];
  if (parameters.length === 0) return "";
  const item = parameters[0];
  // Upstream ChartTooltipContent's default 'dot' indicator is a 10px rounded
  // SQUARE (rounded-[2px], h-2.5 w-2.5), not a circle — see chart.tsx. The
  // engine's `item.color` follows its own multi-hue rotation palette, so the
  // swatch is re-resolved from the single-hue ramp by series position.
  const dot = `<span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:${chartBarSeriesColor(item.seriesIndex ?? 0).css};margin-right:6px;"></span>`;
  const label = escapeTooltipHtml(String(item.seriesName ?? item.name ?? ""));
  const value = escapeTooltipHtml(String(item.value ?? ""));
  return chartBarTooltipRow(dot, label, value);
}

function chartBarValueDomainOptions(values: number[]): {
  min: number;
  max: number;
} {
  const [min, max] = chartBarValueDomain(values);
  return { min, max };
}

export { chartBarDefault };

← Back to shadcn/ui catalog