No framework. No runtime data binding. No virtual DOM diffing a chart shape. And yet this compiles into a full portfolio analytics dashboard — stat cards, a trending line chart with a tooltip, category bars — from nothing but CSS:
<script src="https://cdn.jsdelivr.net/npm/fscss@1.1.24/exec.min.js" async></script>
<style>
@import((*) from st-core)
@st-root()
@st-container(body)
@st-stat-card(.stat-card)
@st-chart-fill(.chart-fill)
@st-chart-line(.chart-line)
@st-chart-dot(.chart-dot, 71, 75)
@st-chart-grid(.chart-grid, 8, 7)
@st-chart-axis-x(.x-axis)
@st-cat-bar-fill(.bar-fill, 0)
.chart {
width: 100%;
height: 200px;
border-radius: var(--st-radius-lg);
position: relative;
overflow: hidden;
background: var(--st-surface);
@st-chart-points(40, 55, 48, 62, 58, 75, 58, 70)
}
</style>
That's not a placeholder — that's the actual chart. No <canvas>, no chart.render(), no dataset passed to a library. So how does it "know" what shape to draw? It doesn't, exactly — and that's the whole trick.
FSCSS never does math. It only writes variables.
Here's the part people usually assume is happening and isn't: FSCSS doesn't calculate a chart shape, evaluate your data, or run any logic in the browser. To prove it, here's the actual compiled output of @st-chart-points(40, 55, 48, 62, 58, 75, 58, 70) — not a guess, copy-pasted straight from the compiler:
.chart {
width: 100%;
height: 200px;
border-radius: var(--st-radius-lg);
position: relative;
overflow: hidden;
background: var(--st-surface);
--st-p1: 60%;
--st-p2: 45%;
--st-p3: 52%;
--st-p4: 38%;
--st-p5: 42%;
--st-p6: 25%;
--st-p7: 42%;
--st-p8: 30%;
}
Eight numbers become eight CSS custom properties, each one just 100 - value. That's the entire "intelligence" of the mixin — a compile-time substitution, nothing more. The actual chart shape comes from .chart-fill and .chart-line, which are static rules, already written, that reference those same eight variables:
.chart-fill {
position: absolute;
inset: 0;
clip-path: polygon(
0% var(--st-p1),
14% var(--st-p2),
28% var(--st-p3),
42% var(--st-p4),
57% var(--st-p5),
71% var(--st-p6),
85% var(--st-p7),
100% var(--st-p8),
100% 100%,
0% 100%
);
background: linear-gradient(180deg, color-mix(in srgb, var(--st-accent) 35%, transparent), transparent);
}
The browser's own CSS engine resolves var(--st-p3) and reflows the polygon — the same mechanism that's always redrawn width: var(--x) on any element, just pointed at shape coordinates instead of a box. FSCSS's job ends the moment those eight variables exist. Everything visual after that is native CSS doing what it was always capable of.
That's why the result still looks intentional and modern despite there being zero runtime logic: the shape math was solved once, by hand, when st-core.fscss was written — gradient fills, glow filters, radius tokens, spacing scale. Your data just slots into variables that were designed around already. You're not generating a design. You're filling in one that already exists.
The same trick shows up everywhere in the module
Once you see the "just compile to a variable" pattern in the chart, you start noticing it's the whole module, not a one-off:
The data-point dot — @st-chart-dot(.chart-dot, 71, 75) compiles to:
.chart-dot {
position: absolute;
left: calc(71% - 6px);
top: calc(25% - 6px);
width: 12px;
height: 12px;
border-radius: 50%;
background: #fff;
border: 2.5px solid var(--st-accent);
}
Same 100 - y math as the chart points (100 - 75 = 25), just applied to a dot's top offset instead of a polygon vertex. One coordinate formula, reused everywhere position needs to invert against a top-down layout.
Category bars — @st-cat-bar-fill(.bar-fill, 0) compiles a base rule:
.bar-fill {
--st-cat-bar-fill-range: 0%;
height: 100%;
width: var(--st-cat-bar-fill-range);
border-radius: 999px;
background: linear-gradient(90deg, var(--st-accent), var(--st-accent-2));
transform-origin: left;
}
The mixin only runs once. Per-bar values like .direct { --st-cat-bar-fill-range: 82%; } aren't separate mixin calls re-triggering anything — they're just normal CSS cascade, a more specific selector overriding a variable's default. FSCSS already stepped out of the picture by the time that resolves.
Two ways to ship it
Runtime / CDN — what's in every example above. FSCSS compiles the <style> block in-browser, once, on load. Fast to prototype with, zero build step, but you're shipping the compiler to the client.
CLI / build-time, for production:
npm install -g fscss
fscss dashboard.fscss dashboard.css
<link rel="stylesheet" href="dashboard.css">
Identical output — the block above is literally what the CLI would emit — computed ahead of time instead of in the browser. No runtime dependency, no parse step on load, best possible performance. This is the mode you want once a project leaves "demo" and becomes production.
Real data still works — because the output is just variables
This is what makes it more than a static mockup: since every mixin bottoms out in plain custom properties, updating the chart with live data doesn't need FSCSS at runtime at all. Plain JavaScript, one line:
document.querySelector('.chart').style.cssText = `
--st-p1: 40%; --st-p2: 75%; --st-p3: 55%;
--st-p4: 60%; --st-p5: 48%; --st-p6: 66%;
--st-p7: 52%; --st-p8: 70%;
`;
The browser repaints the polygon natively — no re-render cycle, no diffing, no library. Pair it with transition: clip-path and updates animate for free. That's the actual split: FSCSS decides the shape language at compile time. JavaScript only ever supplies numbers, whenever your real data shows up.
Example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portfolio Analytics — st-core.fscss demo</title>
<script src="https://cdn.jsdelivr.net/npm/fscss@1.1.24/exec.min.js" async></script>
<style>
@import((*) from st-core)
/* INIT */
@st-root()
@st-container(body)
/* COMPONENTS */
@st-stat-card(.stat-card)
@st-chart-fill(.chart-fill)
@st-chart-line(.chart-line)
@st-chart-dot(.chart-dot, 71, 75)
@st-chart-grid(.chart-grid, 8, 7)
@st-chart-axis-x(.x-axis)
@st-cat-bar-fill(.bar-fill, 0)
/* LAYOUT */
.wrapper {
flex-direction: column;
gap: 20px;
width: 100%;
max-width: 420px;
}
.stat-row {
display: flex;
gap: 12px;
}
.stat-row .stat-card { flex: 1; padding: 16px; }
/* CHART */
.chart {
width: 100%;
height: 200px;
border-radius: var(--st-radius-lg);
position: relative;
overflow: hidden;
background: var(--st-surface);
/* daily visitor count, normalized 0-100 */
@st-chart-points(40, 55, 48, 62, 58, 75, 58, 70)
}
.chart-line {
@st-chart-line-width(2px);
filter: drop-shadow(0 0 8px var(--st-accent));
}
.chart-fill {
opacity: .85;
}
.chart-dot {
position: relative;
overflow: visible;
}
.chart-dot:after {
content: attr(data-point);
background: var(--st-accent);
padding: 6px 8px;
font-size: 12px;
font-weight: 700;
color: #fff;
border-radius: var(--st-radius-sm);
position: absolute;
top: -39px;
left: -30px;
white-space: nowrap;
}
.chart-dot:before {
content: '';
width: 10px;
height: 10px;
background: var(--st-accent);
transform: rotate(45deg);
position: absolute;
top: -18px;
left: 2px;
}
/* TRAFFIC SOURCES */
.sources { display: flex; flex-direction: column; gap: 14px; }
.source-row { display: flex; flex-direction: column; gap: 6px; }
.source-label {
display: flex;
justify-content: space-between;
font-size: 13px;
color: var(--st-text);
}
.source-label span:last-child { color: var(--st-muted); }
.bar-track {
height: 8px;
background: var(--st-accent-dim);
border-radius: 999px;
overflow: hidden;
}
.direct { --st-cat-bar-fill-range: 82%; }
.google { --st-cat-bar-fill-range: 64%; }
.github { --st-cat-bar-fill-range: 41%; }
.twitter { --st-cat-bar-fill-range: 29%; }
h2 {
font-size: 18px;
font-weight: 700;
margin: 0 0 4px;
color: var(--st-text);
}
p.sub {
font-size: 13px;
color: var(--st-muted);
margin: 0 0 8px;
}
.x-axis{
padding-left: 0;
}
</style>
</head>
<body>
<div class="wrapper">
<div>
<h2>Portfolio Analytics</h2>
<p class="sub">Last 8 days</p>
</div>
<div class="stat-row">
<div class="stat-card">
<div class="st-stat-label">TOTAL VISITORS</div>
<div class="st-stat-value">2,148</div>
<div class="st-stat-delta up">+12.4% vs last week</div>
</div>
<div class="stat-card">
<div class="st-stat-label">UNIQUE VISITORS</div>
<div class="st-stat-value">1,602</div>
<div class="st-stat-delta up">+8.9% vs last week</div>
</div>
</div>
<div class="chart">
<div class="chart-fill"></div>
<div class="chart-line"></div>
<div class="chart-dot" data-point="82 visits"></div>
<div class="chart-grid"></div>
</div>
<div class="x-axis">
<span>-6d</span>
<span>-5d</span>
<span>-4d</span>
<span>-3d</span>
<span>-2d</span>
<span>-1d</span>
<span>10h</span>
</div>
<div class="stat-card">
<div class="st-stat-label">AVG. SESSION TIME</div>
<div class="st-stat-value">2m 41s</div>
<div class="st-stat-delta down">-3.2% vs last week</div>
</div>
<div class="stat-card sources">
<div class="st-stat-label">TOP TRAFFIC SOURCES</div>
<div class="source-row">
<div class="source-label"><span>Direct</span><span>82%</span></div>
<div class="bar-track"><div class="bar-fill direct"></div></div>
</div>
<div class="source-row">
<div class="source-label"><span>Google</span><span>64%</span></div>
<div class="bar-track"><div class="bar-fill google"></div></div>
</div>
<div class="source-row">
<div class="source-label"><span>GitHub</span><span>41%</span></div>
<div class="bar-track"><div class="bar-fill github"></div></div>
</div>
<div class="source-row">
<div class="source-label"><span>Twitter</span><span>29%</span></div>
<div class="bar-track"><div class="bar-fill twitter"></div></div>
</div>
</div>
</div>
</body>
</html>
Try it
GitHub Repository: github.com/fscss-ttr/st-core.fscss
If you wire this up to something real — sockets, polling, whatever your data source is — I'd genuinely like to see it. The whole point of keeping the output this dumb is that it's easy to plug into anything.
Top comments (0)