The short answer to “there are no pure CSS chart libraries that can generate radar/spider charts” is: there is one now. And it did not get heavy to do it.
st-core.fscss v2 draws area, line, and spider/radar charts with the same idea:
- FSCSS
@arrfor data - CSS custom properties for coordinates
-
clip-path: polygon()for shapes - Gradients for grid / spokes
No SVG. No canvas. No Chart.js / D3 / Recharts on the critical path.
Repo: github.com/fscss-ttr/st-core.fscss
Requires FSCSS ≥ 1.2.3 (spider helpers ship in v2).
The claim this answers
For a long time the pragmatic take was:
Pure CSS can fake a bar or a simple line. Radar needs polar math, multiple series, spokes, labels — so you need SVG or a library.
That was fair when “CSS charts” meant hand-written polygons or one-off hacks.
st-core@v2 treats radar the same way it treats linear charts: data in → variables out → polygons on screen. Polar is just a different coordinate writer.
Is it still lightweight?
Yes.
st-core is not a monolithic stylesheet you dump on every page.
| Design rule | What it means |
|---|---|
| What you call is what is invoked | Helpers only emit CSS when you call them (@st-spider-fill, @st-spider-line, …). Unused chart families stay out of the output. |
| No third-party chart runtime | No vendor JS, no unused Chart.js themes, no SVG path builder. |
| Selective import | Import only the helpers you need, or compile once with the CLI and ship plain CSS. |
| Tokens, not frameworks | Color, fill opacity, stroke scale are CSS variables (--st-accent, --st-spider-fill-opacity, …). |
In FSCSS terms: @import registers @define helpers. Nothing paints until you call them with a selector. Call spider only → you do not pay for a linear-only dashboard you never used. Call fill + line only → dots and grid stay out.
Compile path (production):
npm install -g fscss@1.2.6
fscss chart.fscss chart.css
Ship chart.css. No client preprocessor. No chart library bundle.
Minimal radar (one series)
<script src="https://cdn.jsdelivr.net/npm/fscss@1.2.6/runtime.min.js" async></script>
<style>
@import((*) from st-core@v2)
@st-root()
@st-spider-root()
@arr skills[80, 65, 90, 55, 70, 85]
@st-spider-fill(.spider-fill, skills)
@st-spider-line(.spider-line, skills)
@st-spider-dots(.spider-dot-, skills, 9px)
@st-spider-grid(.spider-grid)
@st-spider-spokes(.spider-spokes, skills)
.spider {
@st-spider-points(skills, 42)
position: relative;
width: 280px;
height: 280px;
border-radius: 50%;
background: var(--st-surface);
}
</style>
<div class="spider">
<div class="spider-grid"></div>
<div class="spider-spokes"></div>
<div class="spider-fill"></div>
<div class="spider-line"></div>
<div class="spider-dot-1"></div>
<div class="spider-dot-2"></div>
<div class="spider-dot-3"></div>
<div class="spider-dot-4"></div>
<div class="spider-dot-5"></div>
<div class="spider-dot-6"></div>
</div>
Six axes, fill, dual-pass stroke, dots, rings, spokes — still only the helpers you named.
How polar stays pure CSS
One rule (same as linear charts):
Points write. Renderers read.
| Helper | Role |
|---|---|
@st-spider-points(array, rmax) |
On the host: writes --st-sxN / --st-syN via sin / cos in calc()
|
@st-spider-fill / @st-spider-line / @st-spider-dots
|
Read those variables; emit clip-path / positioned dots |
@st-spider-grid |
Concentric rings (radial gradient) |
@st-spider-spokes / @st-spider-spokes-n
|
Radial axes |
Angle 0° is at the top. Radius is a % of the box (rmax, often 42).
/* conceptual output per vertex */
--st-sx1: calc(50% + (r * 1%) * sin(0deg));
--st-sy1: calc(50% - (r * 1%) * cos(0deg));
Polygon vertex count is fixed at compile time from @arr length. Changing values at runtime is free (set the variables). Changing length needs a matching compiled shape (same constraint as linear series).
Multi-series without coordinate clashes
Each series gets its own host and its own @st-spider-points scope:
@arr teamA[92, 78, 85, 55, 48, 80]
@arr teamB[68, 82, 70, 95, 62, 58]
@st-spider-fill(.fill-a, teamA)
@st-spider-line(.line-a, teamA)
@st-spider-fill(.fill-b, teamB)
@st-spider-line(.line-b, teamB)
.spider-wrap.team-a {
@st-spider-points(teamA, 42)
--st-accent: #9d7eff;
--st-spider-fill-opacity: 28%;
}
.spider-wrap.team-b {
@st-spider-points(teamB, 42)
--st-accent: #4fffb0;
--st-spider-fill-opacity: 30%;
}
Shared grid/spokes once; series only differ by tokens and points. Still no chart library.
Live updates (optional JS)
You do not need JS to paint the chart. You only need it if values change after load:
function polarVars(values, rmax = 42) {
const n = values.length;
return values.map((v, i) => {
const ang = (i * 360) / n;
const r = (v / 100) * rmax;
return (
`--st-sx${i + 1}: calc(50% + (${r} * 1%) * sin(${ang} * 1deg));` +
`--st-sy${i + 1}: calc(50% - (${r} * 1%) * cos(${ang} * 1deg));`
);
}).join(' ');
}
document.querySelector('.spider').style.cssText =
polarVars([80, 65, 90, 55, 70, 85]);
That is a few lines of state — not a charting framework.
Why “lightweight” holds after radar
Radar did not force a second architecture:
| Before (linear) | After (spider) |
|---|---|
@arr + --st-pN
|
@arr + --st-sxN / --st-syN
|
clip-path fill / line |
Same, polar vertices |
| Call only the helpers you need | Same |
| Compile to plain CSS | Same |
You did not import “st-core full product suite.” You invoked spider helpers. Unused linear-only pages never see those rules if you selective-import or compile a dedicated sheet.
No unused third-party stylesheets. No hidden JS chart theme. No hydration tax for a static skills matrix.
When pure CSS radar is the right tool
Good fit
- Skills matrices, multi-axis scores, team comparison cards
- Dashboards that already avoid heavy front-end frameworks
- SEO / first paint: real DOM, no canvas blank frame
- Design-system tokens already driving the rest of the UI
Less ideal
- Thousands of points with continuous streaming redraw (canvas wins)
- Interaction-heavy brushing/zoom (libraries still win)
- Dynamic axis count every second (compile-time polygon length)
st-core is honest about that boundary: visualization as styling, not as a full analytics runtime.
Try it
<!--- prototype --->
<script src="https://cdn.jsdelivr.net/npm/fscss@1.2.6/runtime.min.js" async></script>
# production
npm i -g fscss
fscss radar.fscss radar.css
Resources/examples : st-core.fscss README — Spider / Radar
Visual builder: hub.devtem.org/st-core.fscss/visual-builder
Related:
- Pure CSS Spider / Radar Charts Are Here in st-core.fscss v2
- Build a Pure CSS Radar Chart — Lightweight, Max Performance
- Linear charts intro: Pure CSS Charts? Yes, Really.
Radar is no longer proof that pure CSS charts “can’t.” With st-core@v2 it is the same lightweight contract as area and line — what you call is what is invoked, tokens instead of a library, compile when you want zero runtime. MIT · fscss-ttr.
Top comments (0)