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>
);
};