Theming
FundamentalsOne visual language across every engine.
Plotcn uses semantic CSS variables and small engine adapters so Recharts, D3.js, and Google Charts belong to the same application without pretending to share the same rendering API.
var(--chart-1, oklch(0.87 0 0))Plotcn Visual Hierarchy & Token Translation
Single Source of TruthConsumes var(--chart-1) directly on SVG stroke and fill props.
Passes CSS tokens to React JSX paths or interpolates RGB values in math scales.
Translates active CSS theme into strongly-typed Google Chart JavaScript options.
Theming model
Plotcn does not carry an isolated, proprietary theme engine alongside your application. Instead, it forms a cohesive pipeline that translates your application's design system into chart surfaces, gridlines, axis typography, and series marks.
Unified Theming Pipeline
Architecture FlowApp Semantic Tokens
Surface & Structural Baseline
Plotcn Chart Tokens
Categorical Visualization Identity
Engine Token Adapters
Translates CSS Vars to Engine Contracts
Multi-Engine Outputs
Rendered Pixel-Perfect Charts
Inherit the application theme
Plotcn visualizations reuse existing application tokens defined by shadcn/ui and Tailwind CSS. Before creating a chart-specific token, the component inherits standard application variables:
- Background & Card surfaces:
var(--background),var(--card) - Text & Foreground:
var(--foreground),var(--card-foreground) - Muted text & Subtitle labels:
var(--muted-foreground) - Borders & Separators:
var(--border) - Popover overlays:
var(--popover),var(--popover-foreground)
The "Reuse Before Extend" Rule
The "Reuse Before Extend" Mapping Matrix
Zero Design DriftChart cards and canvas backgrounds inherit host container surfaces naturally without explicit repainting.
var(--background) / var(--card)Horizontal and vertical guide lines match host separator borders at a restrained 6–10% opacity.
var(--border) or subtle alphaTimestamps, dollar units, and category ticks inherit secondary label typography and muted contrast.
var(--muted-foreground)Series marks require dedicated OKLCH categorical tokens to maintain visual punch and WCAG contrast.
var(--chart-1)By anchoring charts to your existing tokens, installing Plotcn never overwrites or clashes with your established design system.
Chart-specific tokens
For concerns specific to data visualization—such as distinct series colors and calibrated opacity scales—Plotcn defines a standard set of chart tokens in app/globals.css.
Plotcn Theme Token Registry
app/globals.cssThese standard tokens are calibrated with consistent perceptual lightness across dark and light palettes. They can be inspected, copied, or overridden globally or locally.
Standard series tokens in OKLCH
Plotcn defines 5 categorical series tokens calibrated in the perceptual OKLCH color space to ensure uniform lightness and contrast:
:root { --chart-1: oklch(0.87 0 0); /* Primary leading metric */ --chart-2: oklch(0.556 0 0); /* Secondary comparison */ --chart-3: oklch(0.439 0 0); /* Tertiary series */ --chart-4: oklch(0.371 0 0); /* Supporting category */ --chart-5: oklch(0.269 0 0); /* Baseline context */ --chart-positive: oklch(0.75 0.18 155); /* Semantic gain */ --chart-negative: oklch(0.65 0.22 25); /* Semantic loss */}Distinguishing series identity from semantic status
Never confuse series identity colors (--chart-1 through --chart-5) with semantic indicators (--chart-positive, --chart-negative). If a chart displays three different product tiers, use neutral categorical tones. Reserve green and red exclusively for metrics that carry true positive or negative meaning (e.g. Profit vs. Loss).
Series colors
Different data structures demand different color models. Do not treat all visual data as a categorical list of --chart-1 through --chart-5.
Data Encoding Color Models
Perceptual SemanticsUse when data represents distinct, qualitative categories (e.g. Products, Regions, Marketing Channels). No intrinsic numerical ordering.
Use when data progresses continuously from low to high magnitude (e.g. Heatmaps, Population Density, Choropleth GeoCharts).
Use when data has a meaningful neutral zero or baseline point (e.g. Profit vs. Loss, Budget Variance, Temperature Deviation).
1. Categorical data
Use for independent, qualitative categories where order does not imply value (e.g. Desktop vs. Mobile vs. Tablet traffic). Each series receives a distinct, distinguishable tone from --chart-1 to --chart-5.
2. Sequential data
Use when data encodes a continuous numeric progression from low to high magnitude (e.g. population density choropleths, heatmap intensities). Use a single color scale with graduating lightness or saturation rather than multiple unrelated hues.
3. Diverging data
Use when data measures deviation from a meaningful central baseline or target (e.g. budget variance, quarterly net profit/loss). Two contrasting hues diverge from a neutral midpoint.
Chart colors & customization hierarchy
Plotcn implements a four-tier color resolution hierarchy:
Plotcn theme tokens (app/globals.css) ↓component semantic defaults ↓optional component color overrides (e.g. primaryColor, referenceColor) ↓series-level overrides where supported1. Theme-native defaults
When no color prop is explicitly supplied, components resolve their marks from CSS variables:
color/primaryColordefaults tovar(--chart-1)referenceColor/rangeColordefaults tovar(--chart-2)milestoneColordefaults tovar(--chart-milestone-pin)
This ensures that switching between Light and Dark mode dynamically updates the visualization without hardcoded color lock-in.
2. Component-level semantic overrides
Every chart component exposes semantic color properties reflecting its specific visual architecture:
// Single-series override<SignalLine data={data} xKey="date" color="#2563eb" />// Two-role comparison<TwinlineCompare data={data} xKey="date" primaryColor="#2563eb" referenceColor="#a1a1aa"/>// Forecast with range envelope<RangeLine data={data} xKey="date" color="#2563eb" rangeColor="#93c5fd"/>// Trend with milestones<MilestoneLine data={data} xKey="date" color="#2563eb" milestoneColor="#71717a" milestones={milestones}/>Explicit custom colors remain fixed across Light and Dark theme changes, preserving intentional brand styling while theme presets adapt automatically.
Grid, axes, and labels
Visual hierarchy is essential for readable charts. The data marks must always command visual attention; supporting structure must remain secondary.
Visual Contrast Hierarchy
Readability DisciplinePrimary visual focus. Uncompromised saturation and highest contrast against background surface.
Secondary scanning context. Readable when inspected, but steps back to avoid overpowering data peaks.
Tertiary orientation aids. Must remain quiet; if gridlines are noticed first, the chart is over-emphasized.
Axis typography conventions
- Typography: Axis labels inherit your application's font stack (
var(--font-sans)orvar(--font-mono)). - Color: Rendered in
var(--muted-foreground)so timestamps and unit labels never overpower chart peaks. - Borders: Avoid harsh, solid axis baseline rules unless separating stacked facets.
Tooltips and legends
Engine default tooltips frequently look disjointed from the rest of an application. Plotcn standardizes the visual language of overlays and legends regardless of the underlying chart library.
Standardized Tooltip & Legend Layout
Consistent Popover AnatomyStandardizes font sizing (11-12px), hairline borders (--border), compact spacing, and tabular numeric alignment.
Legends use small, high-contrast series pills that remain fully keyboard focusable and ARIA-attributed if interactive.
Tooltip visual language
- Container: Rendered on
var(--popover)with hairline border (var(--border)) and soft ambient shadow. - Typography: Compact 11–12px font matching your application theme.
- Alignment: Tabular numbers (
font-variant-numeric: tabular-nums) ensure clean column alignment when comparing multi-series metrics. - Indicators: Small, circular color pills matching the series stroke or fill.
Accessible legends
Legend items mirror application badge and pill components. If clicking a legend item toggles series visibility, that item must provide keyboard focus indicators (outline: var(--ring)) and accessible ARIA pressed states.
State theming
Visualization shells must handle intermediate states (loading, empty, error) without breaking visual harmony.
Loading states
Render a neutral skeleton shell or subtle pulsing container on var(--muted). Never render fake metrics or randomized placeholder curves during loading.
Empty states
When an array contains zero records, display a quiet container with an icon and clear message: "No activity recorded for this period." Never render empty axes floating in a void.
Error states
If data fetching or geometry compilation fails, contain the failure within the chart card using ChartBoundary. Display a respectful alert without painting the entire card bright red.
Dark and light modes
Plotcn visualizations adapt automatically when toggling between light, dark, and system themes.
Side-by-Side Surface & Token Comparison
Zero Flicker TransitionSingle application theme toggle
There is no separate "chart theme toggle". Because Plotcn charts bind to CSS variables under :root and .dark, they re-render or restyle instantly alongside the rest of the application.
/* Light Mode */:root { --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --border: oklch(0.922 0 0); --chart-1: oklch(0.205 0 0); /* Deep contrast titanium in light mode */}/* Dark Mode */.dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); --border: oklch(1 0 0 / 10%); --chart-1: oklch(0.87 0 0); /* Crisp high-luminance white in dark mode */}Recharts theming
Recharts components in Plotcn consume CSS variables directly on SVG presentation attributes, eliminating the need for massive JavaScript configuration objects.
import { Area, CartesianGrid, XAxis, YAxis, Tooltip } from "recharts"<CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="3 5"/><XAxis dataKey="label" tick={{ fill: "var(--muted-foreground)", fontSize: 11 }} axisLine={false} tickLine={false}/><Area type="monotone" dataKey="value" stroke="var(--chart-1)" fill="url(#gradient)" strokeWidth={2}/>When the user toggles dark mode, the browser updates CSS variable values instantly without requiring component re-renders or canvas repaints.
D3.js theming
React-rendered D3 visualizations bind CSS variables directly to JSX elements, preserving seamless theme synchronization:
// Direct CSS variable binding on SVG marks<path d={pathGenerator(data) ?? ""} stroke="var(--chart-1)" strokeWidth={2} fill="none"/><line x1={0} x2={width} y1={yPos} y2={yPos} stroke="var(--border)" strokeDasharray="4 4"/>Calculated color scales in D3
When D3 mathematical scales require concrete color values (e.g. scaleLinear().range(["#27272a", "#f4f4f5"]) for continuous heatmaps), avoid scattering getComputedStyle() calls throughout your render loop. Centralize color resolution or use static perceptual token arrays.
Google Charts theming
Google Charts renders inside isolated containers that do not inherit application CSS variables automatically. Plotcn bridges this difference using a dedicated theme adapter.
Reads --background, --foreground, --chart-1 ... --chart-5
Extracts computed HSL/OKLCH color values at runtime
Builds Google ChartOptions (colors, backgroundColor, fontName)
Draws canvas/SVG with correct theme without brittle DOM mutation
The Google theme adapter (lib/google-charts/theme.ts)
export function getGoogleChartTheme(userOptions = {}) { const baseTheme = { backgroundColor: "transparent", colors: ["#f4f4f5", "#a1a1aa", "#71717a", "#52525b", "#3f3f46"], legend: { textStyle: { color: "#a1a1aa", fontSize: 11 } }, hAxis: { textStyle: { color: "#71717a", fontSize: 11 }, gridlines: { color: "rgba(255, 255, 255, 0.06)", count: 5 } }, vAxis: { textStyle: { color: "#71717a", fontSize: 11 }, gridlines: { color: "rgba(255, 255, 255, 0.06)", count: 5 } } } return deepMerge(baseTheme, userOptions)}Redraw on theme change
When the user toggles dark mode, the Google Charts wrapper detects the theme change via MutationObserver or useTheme and triggers chart.draw() with newly resolved options. The external Google script is never reloaded.
Customize the theme
You can customize chart themes globally in your stylesheet or scope specific palettes to individual dashboard cards.
1. Global palette customization
To change your application's default chart palette, update the tokens in app/globals.css:
:root { /* Custom Emerald / Teal palette */ --chart-1: oklch(0.7 0.15 155); --chart-2: oklch(0.6 0.14 175); --chart-3: oklch(0.5 0.12 195);}2. Scoped card overrides
To apply a custom palette to a single chart (e.g. a financial report card), set scoped CSS variables directly on the container:
<div className="rounded-xl border border-border p-6" style={{ "--chart-1": "oklch(0.75 0.18 155)", // Positive gain "--chart-2": "oklch(0.65 0.22 25)", // Negative loss } as React.CSSProperties}> <PlotLineChart data={financialData} /></div>Customization Escalation Hierarchy
Customization Escalation Hierarchy
Follow the principle of progressive specificity: solve styling with tokens first before reaching for inline styles or source modifications.
app/globals.cssSet once in globals.css; all charts inherit theme surfaces, text, and borders automatically.
app/globals.css (:root & .dark)Calibrate --chart-1 through --chart-5 in OKLCH to define your product's visualization identity.
Parent Container style={{ ... }}Inject CSS variable overrides onto a parent card or wrapper to localize visual exceptions.
components/charts/plot-line-chart.tsxEdit the installed component file directly to customize SVG markup, math, gradients, and animations.
Theme the application, not every chart independently. Plotcn charts automatically inherit standard shadcn/ui variables (--background, --foreground, --card, --border). Updating your primary brand or card surface synchronizes every chart in your application with zero visual drift.
/* app/globals.css */
:root {
--background: oklch(0.14 0 0);
--foreground: oklch(0.98 0 0);
--card: oklch(0.18 0 0);
--border: oklch(0.24 0 0);
}Semantic color
Color in data visualization is an informational encoding, not mere decoration.
- Category: Identifies distinct series without implying hierarchy.
- Magnitude: Darker or more saturated hues convey higher intensity.
- Direction: Diverging scales measure deviation from a target or zero line.
- Status: Positive, negative, or warning thresholds.
Contrast and accessibility
All Plotcn theme tokens adhere strictly to WCAG visualization accessibility guidelines.
Theme Contrast & Accessibility Verification
WCAG CompliantMeets 4.5:1 text contrast against --background in both light and dark modes.
Stays within 10-20% opacity to prevent visual clutter or competition with data marks.
Provides secondary encoding (stroke dashes, dot shapes, or direct labels) alongside color.
Interactive chart elements carry visible outline using var(--ring) on keyboard tab.
Rendered with high-contrast font and explicit solid background on var(--popover).
Respects prefers-reduced-motion by disabling animations during theme switches.
Key contrast rules
- Text contrast: All axis labels, tick marks, and tooltip text must meet at least 4.5:1 contrast against
--backgroundor--popover. - Adjacent marks: Adjacent bars or stacked slices must maintain discernible lightness differences or thin separating borders (
--border). - Focus rings: Interactive data elements must display visible outlines using
var(--ring)on keyboard focus.
Motion and emphasis
Plotcn emphasizes visual hierarchy through typography, opacity, and stroke weight rather than excessive bright neon colors or distracting animations.
- Stroke Weight: Primary metric lines use
strokeWidth={2}; comparison benchmarks usestrokeWidth={1.5}with dash patterns (strokeDasharray="4 5"). - Soft Fills: Area gradients use soft alpha stops (
stopOpacity={0.16}tapering to0) to ground charts without obscuring gridlines. - Reduced Motion: All transitions respect the
prefers-reduced-motionmedia query by disabling entry animations during theme switches.
Common mistakes
Diagnostic guidance for common visualization theming pitfalls:
Common Theming Pitfalls & Architectural Solutions
Diagnostic ChecklistHardcoding Hex Colors in Components
PROBLEM:Every chart file contains '#18181b', '#71717a', etc. Changes require editing dozens of files.
WHY:Circumvents the centralized design system.
Using One Palette for All Data Types
PROBLEM:Categorical series, heatmaps, and profit/loss graphs all reuse the same five unrelated colors.
WHY:Conflates qualitative identity with quantitative magnitude.
Engine Default Styles Leak In
PROBLEM:Recharts looks on-brand, but Google Charts looks like a stock blue-and-red Google demo.
WHY:Google Charts requires an explicit options theme adapter.
Overpowering Grid Contrast
PROBLEM:Dark, thick gridlines dominate the chart and make thin lines or small points hard to distinguish.
WHY:Grid is drawn with high contrast instead of subtle alpha.
Confusing Series Identity with Status
PROBLEM:Green is assigned to 'Product Series B', confusing users who interpret green as 'Healthy / Success'.
WHY:Mixing decorative categorical colors with semantic indicator tokens.
Inconsistent Tooltip Appearance
PROBLEM:Recharts renders custom HTML tooltips while D3 renders browser title tags or disparate boxes.
WHY:Lack of a shared tooltip styling convention.
Inline Theme Logic in Every Component
PROBLEM:Every chart component imports 'useTheme' and runs manual color branching.
WHY:Unnecessary JavaScript overhead and hydration mismatch risk.
Global Overwrites That Break UI
PROBLEM:Overriding '--chart-1' accidentally changes unrelated button or sidebar styles.
WHY:Chart tokens were incorrectly mapped to global brand tokens.
Recommended workflow
Follow this 10-step sequence when establishing or customizing your visualization theme:
10-Step Theming Implementation Sequence
Design System WorkflowInspect existing --background, --foreground, and --border tokens in globals.css
Inherit application surface and text colors before adding custom variables
Set calibrated --chart-1 through --chart-5 for series in OKLCH
Provide high-contrast values under both :root and .dark selectors
Pass CSS variables directly to stroke and fill props on chart marks
Consume tokens in React SVG or resolve computed RGB values for scales
Pass typed theme object from lib/google-charts/theme.ts
Style overlay popovers to match application popover tokens
Verify 4.5:1 text contrast and ensure colors are not the sole encoding
Commit tokens to version control with zero manual chart repainting
Next steps
Now that you understand Plotcn's theming system and token architecture, explore accessible visualization practices: