chartBarLabel
A Charts block/component from shadcn/ui — clean-room reimplemented for Domphy (see methodology). Call chartBarLabel() with no arguments for a working demo, or edit the code below live.
Props
| Prop | Type | Description |
|---|---|---|
data | ChartBarPoint[] | — |
seriesLabel | string | — |
seriesColor | ThemeColor | — |
labelFormatter | (value: number) => string | Formats each bar's printed value label. Defaults to the raw number. |
title | string | — |
subtitle | string | — |
trendText | string | — |
trendDirection | ChartTrendDirection | — |
captionText | string | — |
height | number | — |
Implementation notes
Uses @domphy/chart's native bar label option (show, position:'top', formatter) — this is genuinely engine-supported (packages/chart/src/overlay/labels.ts renderBarLabels), so value labels are real, not an overlay hack. Y-axis fully hidden, vertical split lines only, x-axis month labels with no ticks. Label text color/font-size are the engine's own fixed defaults (renderBarLabels hardcodes the label color and only reads fontSize, not a custom color, from LabelOption) — close enough to the spec's 'foreground-colored ~12px text' that no override was needed. Direct-source-diff fix (2026-07-05): Same gridline inversion as chartBarMultiple (vertical instead of horizontal) — fixed, plus disabled the hover cursor to match upstream.
Status: ported · Reference: shadcn/ui original
// shadcn/ui "chart-bar" (label recipe) — clean-room reimplementation.
//
// The same single-series monthly bar chart as the default recipe, but every
// bar prints its exact value as a text label just above its top edge — the
// y-axis is fully hidden since the labels take over that role, with only
// horizontal gridlines behind the bars.
//
// Implemented purely from the block's public functional/visual spec — no
// upstream shadcn/ui source was viewed or copied.
import type { ChartOption, LabelParams, TooltipParams } from "@domphy/chart";
import type { DomphyElement } from "@domphy/core";
import {
CHART_BAR_MONTHLY_DATA,
type ChartBarPoint,
type ChartTrendDirection,
chartBarCardShell,
chartBarCategoryXAxis,
chartBarFrame,
chartBarSeriesColor,
chartBarTooltipRow,
chartBarTrendFooter,
chartBarValueDomain,
} from "./chart-bar-shared.js";
export interface ChartBarLabelProps {
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;
/** Formats each bar's printed value label. Defaults to the raw number. */
labelFormatter?: (value: number) => string;
title?: string;
subtitle?: string;
trendText?: string;
trendDirection?: ChartTrendDirection;
captionText?: string;
height?: number;
}
/**
* shadcn/ui "chart-bar" label recipe — every bar's value is printed above
* it. Call with no arguments for a working demo.
*/
function chartBarLabel(props: ChartBarLabelProps = {}): DomphyElement<"div"> {
const {
data = CHART_BAR_MONTHLY_DATA,
seriesLabel = "Desktop",
seriesColor = chartBarSeriesColor(0).css,
labelFormatter = (value) => String(value),
title = "Bar Chart - Label",
subtitle = "January - June 2026",
trendText = "Trending up by 5.2% this month",
trendDirection = "up",
captionText = "Showing total visitors for the last 6 months",
height = 64,
} = props;
const categories = data.map((point) => point.label);
const values = data.map((point) => point.value);
const [, domainMax] = chartBarValueDomain(values, 0.22);
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: chartBarLabelTooltipFormatter,
},
xAxis: chartBarCategoryXAxis(categories),
yAxis: {
type: "value",
min: 0,
max: domainMax,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false },
splitLine: { show: true },
},
grid: { left: 8, right: 8, top: 24, bottom: 32 },
series: [
{
type: "bar",
name: seriesLabel,
color: seriesColor,
label: {
show: true,
position: "top",
fontSize: 12,
formatter: (parameters: LabelParams) =>
labelFormatter(Number(parameters.value) || 0),
},
data: values,
},
],
};
return chartBarCardShell({
title,
subtitle,
content: { div: [chartBarFrame(option, { height })] },
footer: chartBarTrendFooter({
trendText,
direction: trendDirection,
captionText,
}),
});
}
function escapeTooltipHtml(text: string): string {
return text
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
function chartBarLabelTooltipFormatter(
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);
}
export { chartBarLabel };