Most developers reach for a chart library the moment they need a visualization — Chart.js, Recharts, D3 — and suddenly their page is carrying a hefty bundle just to draw a few lines. What if CSS alone could handle it?
That's exactly what st-core.fscss pulls off. It renders fully functional line charts using nothing but browser-native CSS features, compiled at build time.
The Mechanism Behind It
Three CSS primitives do all the heavy lifting:
-
clip-path: polygon()— shapes each line visually -
CSS custom properties (
--st-p1through--st-p8) — hold the data points - FSCSS mixins — generate the chart structure during compilation
The data pipeline looks like this:
data → CSS variables → clip-path → rendered chart
Everything is resolved before the browser even touches it. No runtime rendering, no extra DOM depth, no JavaScript dependency for visuals.
Rendering Multiple Lines
The multi-line chart is a great showcase of how elegantly this scales. The approach uses one shared renderer with per-element data overrides — each line element carries its own dataset via scoped CSS variables.
<script src="https://cdn.jsdelivr.net/npm/fscss@1.1.24/exec.min.js" async></script>
<style>
@import((*) from st-core)
@st-root()
.chart {
height: 200px;
position: relative;
@st-chart-points(20, 25, 21, 37, 30, 60, 27, 50)
}
@st-chart-line(.chart-line)
.chart-line {
background: currentColor;
@st-chart-line-width(2px);
}
.line-1 { color: #32D8D4; }
.line-2 {
color: #E8A030;
@st-chart-points(10, 20, 16, 15, 66, 50, 80, 54)
}
.line-3 {
color: #B840C8;
@st-chart-points(5, 39, 20, 30, 27, 70, 60, 70)
}
@st-chart-grid(.chart-grid, 10, 7)
@st-chart-axis-y(.y-axis)
@st-chart-axis-x(.x-axis)
</style>
<div class="chart">
<div class="chart-line line-1"></div>
<div class="chart-line line-2"></div>
<div class="chart-line line-3"></div>
<div class="chart-grid"></div>
<div class="y-axis">
<span>0</span><span>20</span><span>40</span>
<span>60</span><span>80</span><span>100</span>
</div>
</div>
<div class="x-axis">
<span>Sun</span><span>Mon</span><span>Tue</span>
<span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span>
</div>
Each .line-* element overrides the default dataset from .chart. The renderer picks up whichever --st-p* variables are in scope for that element. Clean, composable, predictable.
Available Mixins at a Glance
| Mixin | What it renders |
|---|---|
@st-chart-line |
Line path renderer |
@st-chart-fill |
Area fill beneath the line |
@st-chart-dot |
Data point markers |
@st-chart-grid |
Background grid overlay |
@st-chart-axis-x / @st-chart-axis-y
|
Axis label layouts |
All of these compile down to plain CSS — nothing ships to the browser that wasn't already resolved.
Handling Dynamic Data
For use cases where data changes at runtime, you can push updated values directly from JavaScript:
chart.style.cssText = `
--st-p1: 40%;
--st-p2: 75%;
--st-p3: 60%;
`;
JavaScript passes the values; CSS renders the result. Transitions work as expected if you've defined them — no additional wiring needed.
Why This Approach Stands Out
- Zero runtime overhead — charts are compiled into static CSS, not computed on each render
- No third-party bundle — the browser's rendering engine does the visual work natively
- Plain custom properties — data lives in CSS, not buried inside a config object or framework component
- Full stylistic control — nothing is locked into a preset theme or opinionated design system
Try It
Live demo: fscss-ttr.github.io/st-core.fscss/multi-chart
Source: github.com/fscss-ttr/st-core.fscss
It's a refreshingly minimal take on data visualization — no installs, no configuration overhead, just CSS doing what it was always capable of.

Top comments (2)
This is honestly refreshing — I’ve shipped dashboards where Chart.js felt like overkill for simple visuals. Using pure CSS like this keeps things lightweight and easier to control. Curious how it holds up with real-time updates at scale though 👀
thats reality