You don’t always need a chart library.
st-core.fscss v2 turns a list of numbers into area fills, strokes, and dots using CSS clip-path — no SVG, no canvas, no chart runtime.
Open source, MIT: github.com/fscss-ttr/st-core.fscss
The idea
- Put your series in an FSCSS array.
- Normalize heights so “90” sits near the top of the chart.
- Let mixins build even X spacing and polygons for you.
CSS paints the shape. Optional JS only updates CSS variables when data changes.
Setup
<script src="https://cdn.jsdelivr.net/npm/fscss@1.2.3/runtime.min.js" async></script>
@import((*) from st-core@v2)
@st-root()
Use FSCSS ≥ 1.2.3. Production tip: compile with the CLI and ship plain CSS — zero runtime.
Minimal plot
@arr myData[20, 45, 28, 80, 65, 90, 40]
@st-chart-fill(.chart-fill, myData)
@st-chart-line(.chart-line, myData)
.chart {
@st-chart-points(myData)
position: relative;
height: 200px;
max-width: 400px;
background: var(--st-surface);
border-radius: 16px;
}
<div class="chart">
<div class="chart-fill"></div>
<div class="chart-line"></div>
</div>
What happens:
-
@st-chart-pointswrites--st-p1…nasnum(100 - value)%(human score → CSS Y). - Fill and line use those variables and space X by
(i - 1) / (N - 1). - Any length of array works — not only eight points (that was v1).
Why invert with num(100 - v)?
In CSS, 0% is the top. A high score should look high on the chart, so:
value 90 → top ≈ 10%
st-core does that for you inside @st-chart-points. You keep thinking in normal 0–100 data.
Add a highlight dot
@st-chart-dot(.chart-dot, 70, 60, 12px)
<div class="chart">
<div class="chart-fill"></div>
<div class="chart-line"></div>
<div class="chart-dot"></div>
</div>
x is % across the width; y is the same human scale as the series.
Live updates (optional JS)
Drawing stays CSS. JS only sets variables:
const fill = document.querySelector('.chart-fill');
const line = document.querySelector('.chart-line');
const normalize = (n) => (100 - n) + '%';
function updatePoints(points) {
const vars = points
.map((v, i) => `--st-p${i + 1}: ${normalize(v)};`)
.join(' ');
fill.style.cssText = vars;
line.style.cssText = vars;
}
updatePoints([40, 75, 60, 45, 80, 50]);
Add transition: clip-path 0.6s ease on fill/line for smooth motion.
Two series
Give each series its own points + accent:
@arr seriesA[30, 50, 75, 40, 85, 60]
@arr seriesB[10, 25, 45, 20, 55, 30]
@st-chart-fill(.fill-a, seriesA)
@st-chart-line(.line-a, seriesA)
@st-chart-fill(.fill-b, seriesB)
@st-chart-line(.line-b, seriesB)
.fill-a, .line-a {
@st-chart-points(seriesA)
--st-accent: #9d7eff;
}
.fill-b, .line-b {
@st-chart-points(seriesB)
--st-accent: #4fffb0;
opacity: 0.85;
}
Dashboard pieces
Same module also ships shell UI:
-
@st-stat-card— label / value / delta -
@st-phone— device frame -
@st-chart-grid/ axis helpers
Handy for a small analytics card without a second library.
Learn more
| Resource | Link |
|---|---|
| Repo | st-core.fscss |
| Under the hood | EXPLAINED.md |
| FSCSS | docs |
| Syntax highlights | Vscode Extention |
Contributions and issues are welcome on the repo — charts should be boring to maintain and fun to theme.
Plot in CSS. Keep data in variables. Let the browser paint.
Top comments (0)