Generate dark-mode-compatible inline SVG data visualization charts for blog posts. Supports horizontal bar, grouped bar, donut, line, lollipop, area, and radar charts with automatic platform detection (HTML vs JSX/MDX). Enforces chart type diversity, accessible markup (role=img, aria-labelledby), source attribution, and transparent backgrounds. Use when user says "blog chart", "generate chart", "data visualization", "svg chart", "blog graph", or "visualize data".
Generates dark-mode-compatible inline SVG charts for blog posts. Invoked
internally by blog-write and blog-rewrite when chart-worthy data is
identified. Not a standalone user-facing command.
Styling source of truth: skills/blog/references/visual-media.md
For supported chart types, prefer the deterministic CLI:
python3 skills/blog-chart/scripts/generate_chart_svg.py --input chart.json --output chart.html --jsonThe writer or researcher passes a chart request:
Chart Request:
- Type: horizontal bar
- Title: "Quarterly Signups by Product"
- Data: Product A 420, Product B 315, Product C 180
- Source: [Verified source], [publication date]
- Platform: mdx (or html)Select based on the data pattern. Prefer chart type diversity, but repeat a type when comparability or reader comprehension clearly benefits.
| Data Pattern | Best Chart Type |
|---|---|
| Before/after comparison | Grouped bar chart |
| Ranked factors / correlations | Lollipop chart |
| Parts of whole / market share | Donut chart |
| Trend over time | Line chart |
| Percentage improvement | Horizontal bar chart |
| Distribution / range | Area chart |
| Multi-dimensional scoring | Radar chart |
All charts must work on both dark and light backgrounds:
Text elements: fill="currentColor"
Grid lines: stroke="currentColor" opacity="0.08"
Axis lines: stroke="currentColor" opacity="0.3"
Background: transparent (no fill on root SVG)
Subtitle text: fill="var(--chart-muted, currentColor)"
Source text: fill="var(--chart-muted, currentColor)"
Label text: fill="currentColor" opacity="0.8"Set --chart-muted to an accessible text token in the host theme. If no token
exists, use #4b5563 on light backgrounds and #d1d5db on dark backgrounds.
Do not rely on low-opacity source or subtitle text for visible attribution.
| Color | Hex | Use Case |
|---|---|---|
| Orange | #f97316 | Primary / highest value |
| Sky Blue | #38bdf8 | Secondary / comparison |
| Purple | #a78bfa | Tertiary / special category |
| Green | #22c55e | Quaternary / positive indicator |
For text inside approved colored elements: use fill="#111827" with
fontWeight="800". Only use white text after checking the contrast ratio is
at least 4.5:1 against that specific fill color.
Do not rely on color alone. Add direct labels, patterns, line dashes, marker shapes, or legend text so colorblind readers can distinguish series.
<svg
viewBox="0 0 560 380"
style="max-width: 100%; height: auto; font-family: 'Inter', system-ui, sans-serif"
role="img"
aria-labelledby="chart-title chart-desc"
>
<title id="chart-title">Chart Title</title>
<desc id="chart-desc">Description for screen readers with all key data points and source</desc>
<!-- Chart content -->
<text x="280" y="372" text-anchor="middle" font-size="10" fill="var(--chart-muted, currentColor)">
Source: Source Name (Year)
</text>
</svg><svg
viewBox="0 0 560 380"
style={{maxWidth: '100%', height: 'auto', fontFamily: "'Inter', system-ui, sans-serif"}}
role="img"
aria-labelledby="chart-title chart-desc"
>
<title id="chart-title">Chart Title</title>
<desc id="chart-desc">Description for screen readers</desc>
{/* Chart content */}
<text x="280" y="372" textAnchor="middle" fontSize="10" fill="var(--chart-muted, currentColor)">
Source: Source Name (Year)
</text>
</svg>| HTML | JSX |
|---|---|
stroke-width | strokeWidth |
stroke-dasharray | strokeDasharray |
stroke-linecap | strokeLinecap |
text-anchor | textAnchor |
font-size | fontSize |
font-weight | fontWeight |
font-family | fontFamily |
class | className |
style="..." | style={{...}} |
Best for: percentage improvements, single-metric comparisons.
chartHeight / dataCount - gap (gap=8)(value / maxValue) * chartWidthy = chartY + index * (barHeight + gap)Best for: before/after, A vs B comparisons.
Best for: parts of whole, market share.
<path d="M... A... L... A... Z" fill="color" />Best for: trends over time.
stroke="currentColor" opacity="0.08"<circle cx=... cy=... r="4" fill="color" /><polyline points="..." fill="none" stroke="color" strokeWidth="2" />opacity="0.1"Best for: ranked factors, correlations.
stroke="currentColor" opacity="0.15" strokeWidth="1"r="6" with fill colorBest for: distribution, cumulative data.
<path d="M... L... L... Z" fill="color" opacity="0.15" />stroke="color" strokeWidth="2" fill="none"Best for: multi-dimensional scoring (5-7 axes).
fill="color" opacity="0.2" stroke="color"<tspan> lines.<desc> or adjacent prose.max-width: 100%; height: auto
style, or choose a justified wider viewBox for dense labels.Wrap every chart in a <figure> element:
HTML:
<figure>
<svg viewBox="0 0 560 380" style="max-width: 100%; height: auto; font-family: 'Inter', system-ui, sans-serif" role="img" aria-labelledby="chart-title chart-desc">
<title id="chart-title">[Chart Title]</title>
<desc id="chart-desc">[Full description with data points for screen readers]</desc>
<!-- chart content -->
<text x="280" y="372" text-anchor="middle" font-size="10" fill="var(--chart-muted, currentColor)">
Source: [Source Name] ([Year])
</text>
</svg>
<figcaption>Source: <a href="[Source URL]">[Source Name]</a>, [publication date].</figcaption>
</figure>MDX:
<figure className="chart-container" style={{margin: '2.5rem 0', textAlign: 'center', padding: '1.5rem', borderRadius: '12px'}}>
<svg viewBox="0 0 560 380" style={{maxWidth: '100%', height: 'auto', fontFamily: "'Inter', system-ui, sans-serif"}} role="img" aria-labelledby="chart-title chart-desc">
<title id="chart-title">[Chart Title]</title>
<desc id="chart-desc">[Full description]</desc>
{/* chart content with camelCase attributes */}
<text x="280" y="372" textAnchor="middle" fontSize="10" fill="var(--chart-muted, currentColor)">
Source: [Source Name] ([Year])
</text>
</svg>
<figcaption>Source: <a href="[Source URL]">[Source Name]</a>, [publication date].</figcaption>
</figure><figcaption> presentrole="img" and aria-labelledby present on <svg><title id> and <desc id> present inside <svg>0 0 560 380 (standard) or justified alternativeaec971a
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.