chart-builder
Generates dark-themed, accessible data visualization components using Chart.js or Recharts. Produces glass morphism styled charts that match the user's design system with proper accessibility, responsive sizing, and performance optimization. Triggered when the user asks to create a chart, visualize data, add a graph, or build a dashboard component.
Chart Builder Skill
Dark-themed, accessible data visualization generator.
When to Trigger
Activate this skill when the user:
- Asks to create a chart, graph, or data visualization
- Wants to visualize data from an API, database, or static dataset
- Requests a dashboard component with charts
- Needs to display metrics, trends, or comparisons visually
Stack Selection
| Scenario | Library | Reason |
|---|---|---|
| Standalone chart, no React | Chart.js | Lightweight, canvas-based |
| React/Next.js project | Recharts | Native React components, composable |
| Complex interactive dashboards | Recharts | Better state management integration |
| Static image export needed | Chart.js | Canvas toDataURL() support |
Design System
All charts must follow these design tokens to match the user's dark theme:
Colors
const chartTheme = {
background: '#18181B', // zinc-900
surface: 'rgba(255,255,255,0.05)', // glass bg
border: 'rgba(255,255,255,0.10)', // glass border
gridLines: 'rgba(255,255,255,0.06)',
labelText: '#D4D4D8', // zinc-300
valueText: '#F4F4F5', // zinc-100
tooltip: {
bg: 'rgba(24,24,27,0.90)',
border: 'rgba(255,255,255,0.10)',
blur: '16px',
},
};
Accent Palette
Use these for data series — ordered by visual priority:
| Index | Name | Hex | Use case |
|---|---|---|---|
| 0 | Blue | #3B82F6 | Primary metric |
| 1 | Purple | #8B5CF6 | Secondary metric |
| 2 | Emerald | #10B981 | Positive/growth |
| 3 | Amber | #F59E0B | Warning/attention |
| 4 | Rose | #F43F5E | Negative/decline |
| 5 | Cyan | #06B6D4 | Tertiary metric |
Glass Morphism Container
Wrap every chart in a glass container:
<div className="backdrop-blur-xl bg-white/5 border border-white/10 rounded-2xl p-6">
<h3 className="text-zinc-100 text-lg font-semibold mb-4">{title}</h3>
<div className="min-h-[300px]" role="img" aria-label={ariaLabel}>
{/* Chart component */}
</div>
</div>
Chart Types
Line Chart
Best for: trends over time, continuous data.
- Use gradient fill below the line (
linearGradientfrom accent to transparent) - Smooth curves:
type="monotone"(Recharts) ortension: 0.4(Chart.js) - Show data points on hover only (reduce visual clutter)
Bar Chart
Best for: comparisons between categories.
- Rounded top corners:
radius={[4, 4, 0, 0]} - Max bar width: 48px (prevent oversized bars on few data points)
- Add value labels above bars for small datasets (under 8 items)
Area Chart
Best for: volume/magnitude over time.
- Stack multiple areas for composition views
- Use decreasing opacity for stacked layers (0.6, 0.4, 0.2)
- Gradient fill from accent color to transparent
Pie / Donut Chart
Best for: part-to-whole relationships (max 6 segments).
- Donut preferred over pie (inner radius 60%)
- Active segment: slight outward offset on hover
- Center label: total value or primary metric
- Legend positioned below on mobile, right on desktop
Scatter Chart
Best for: correlation between two variables.
- Variable dot size for third dimension (bubble chart)
- Add quadrant lines if meaningful thresholds exist
- Tooltip shows both axis values plus label
Radar Chart
Best for: multi-dimensional comparison (3-8 axes).
- Filled area with low opacity (0.2)
- Grid lines match the dark theme
- Limit to 2-3 overlapping datasets for readability
Accessibility Requirements
Every chart must include:
-
ARIA attributes:
<div role="img" aria-label="Monthly revenue trend showing 23% growth from January to June 2025"> -
Reduced motion:
@media (prefers-reduced-motion: reduce) { .chart-container * { animation: none !important; transition: none !important; } } -
Color independence: Never rely on color alone to convey meaning. Add:
- Pattern fills or dash styles for line differentiation
- Direct labels or legends with text descriptions
- Shape variation for scatter plots
-
Screen reader fallback: Include a hidden data table:
<table className="sr-only"> <caption>Monthly revenue data</caption> {/* Full data in tabular form */} </table> -
Contrast: All text on the chart must meet WCAG AA (4.5:1 ratio against
zinc-900).
Responsive Behavior
- Charts fill their container width (
responsive: true/width="100%") - Set
min-h-[300px]on the wrapper to prevent CLS - On mobile (< 640px): move legends below the chart, reduce font sizes
- On print: switch to light theme with black text (via
@media print)
Performance
Charts are heavy — always lazy load:
import dynamic from 'next/dynamic';
const RevenueChart = dynamic(() => import('@/components/charts/RevenueChart'), {
loading: () => (
<div className="backdrop-blur-xl bg-white/5 border border-white/10 rounded-2xl p-6 min-h-[300px] animate-pulse" />
),
ssr: false,
});
- Never put chart components in the LCP critical path
- Use
ssr: false— canvas/SVG charts need the DOM - Skeleton matches the glass container dimensions exactly
Tooltip Styling
const CustomTooltip = ({ active, payload, label }) => {
if (!active || !payload) return null;
return (
<div className="backdrop-blur-xl bg-zinc-900/90 border border-white/10 rounded-lg px-3 py-2 shadow-xl">
<p className="text-zinc-400 text-xs mb-1">{label}</p>
{payload.map((entry, i) => (
<p key={i} className="text-zinc-100 text-sm font-medium">
<span style={{ color: entry.color }}>●</span> {entry.name}: {entry.value}
</p>
))}
</div>
);
};