st-core.fscss started as an FSCSS module with a clear bet: charts can be browser paint, not a JavaScript chart runtime. No canvas. No SVG dependency for the line. Data lives in CSS custom properties; shapes are clip-path: polygon(); optional JS only writes numbers. Compiled, it’s on the order of ~0.5 kb of CSS—not a 40–150 kb chart library.
That idea spread—writeups, demos, dashboard experiments—because it matched what performance-minded frontends already wanted: first paint is the chart, crawlers see markup, updates can be variable writes with free CSS transitions.
ProvChart is not a rejection of that model. It’s what you build when the same philosophy has to survive arbitrary series lengths, multi-series layouts, and a server-side compile step—without asking every team to hand-author mixins per dataset.
What st-core actually nailed
st-core’s contract is intentionally tight and easy to teach:
- Eight points:
--st-p1…--st-p8 - Fixed X stops:
0% 14% 28% 42% 57% 71% 85% 100% - Mixins like
@st-chart-points,@st-chart-line,@st-chart-fill, dots, stats - Optional JS:
element.style.setProperty('--st-p3', '42%')→ browser repaints the polygon
.chart {
position: relative;
height: 200px;
/* FSCSS: @st-chart-points(20, 35, 33, 30, 48, 35, 66, 37) */
}
That is still a strong starter library for hand-built cards, marketing strips, and demos where eight samples are enough. It maximizes what the browser already does well. Nothing about ProvChart throws that away.
Where a fixed eight-point contract stops fitting
Real product data rarely arrives as “exactly eight numbers”:
- Days in a month, weeks in a year, irregular sensor samples
- Several series on one frame (revenue vs cost, traffic vs signups)
- Automation: JSON from an API, not a mixin edited in a stylesheet
st-core’s elegance is that compile-time contract. ProvChart’s job is to keep data → variables → CSS geometry → paint when the point count and series count are decided per request, not per mixin signature.
What ProvChart changes (and what it doesn’t)
| st-core.fscss | ProvChart | |
|---|---|---|
| Paint model | CSS variables + clip-path / native CSS |
Same family of ideas |
| Point count | Eight fixed slots | Length of series[].points (no eight-slot ceiling) |
| Series | Hand composition / multi-line patterns in FSCSS | First-class multi-series, combo, bar, hbar, gauge, … |
| Authoring | Mixins in source / FSCSS compile | JSON → builder or Developer API |
| Client chart library | None | None for paint |
| Docs embeds | Manual / CSS | Optional generate-svg
|
| Interaction | Hand dots / attr() patterns |
Optional provchart-runtime |
Unchanged belief: the chart is CSS. JavaScript should not be required to draw the shape.
Changed machinery: compilation can run on a server; geometry is generated for the payload you sent; roots are instance-scoped (classes like pc-… and variables such as --pc-{id}-s{series}-p{index}) so many charts can share a page without colliding.
There is no marketed “must be ≤ 200 points” product rule in this writeup. Practical limits are the usual ones: payload size, CSS complexity, and what stays smooth in real browsers—not an eight-argument mixin.
Server compile instead of only a <style> mixin
st-core path: FSCSS CLI or in-browser compile → you ship CSS you authored.
ProvChart path: send JSON, get markup:
const res = await fetch("https://provchart-api.devtem.org/api/v1/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.PROVCHART_API_KEY,
},
body: JSON.stringify({
type: "line",
series: [
{ name: "Revenue", color: "#8b7bff", points: [20, 35, 45, 30, 50] },
],
axisX: ["Jan", "Feb", "Mar", "Apr", "May"],
}),
});
const data = await res.json();
// data.html + data.css → inject; first paint is the chart
Five points or fifty—it’s the same call shape. For README and Markdown surfaces, POST /api/v1/generate-svg returns SVG / data URI without requiring a chart runtime on the doc page.
provchart-runtime: interaction without owning paint
st-core already showed the interaction seed: a dot, a tooltip, data on the element, CSS doing the rest.
provchart-runtime (MIT, npm provchart-runtime) generalizes that for generated HTML charts:
- Finds
[data-provchart] - Skips pure SVG exports when configured
- Scroll reveal, legend focus, per-point tooltips (reading generated custom properties where present)
-
MutationObserverfor charts injected after load
<script>
window.ProvChartRuntimeConfig = {
tooltips: true,
perPointTooltips: true,
observe: true,
excludeSvg: true,
};
</script>
<script
src="https://cdn.jsdelivr.net/npm/provchart-runtime@1.0.0/dist/provchart-runtime.min.js"
defer
></script>
Additive on purpose: generate output does not depend on the runtime; the runtime does not replace the engine.
The stack, honestly drawn
st-core.fscss (open source, viral pure-CSS chart idea)
--st-p1…p8 · fixed X-stops · mixins · ~0.5kb compiled CSS
JS optional: write variables, CSS transitions animate polygons
│
▼
ProvChart (chart.devtem.org)
Same paint philosophy · variable-length series · multi-series types
Builder + Developer API → static HTML/CSS (and SVG for docs)
│
▼
provchart-runtime (optional)
Tooltips / motion / legend UX on HTML charts only
- Open core: github.com/fscss-ttr/st-core.fscss
- Product: chart.devtem.org
- Runtime: github.com/fscss-ttr/provchart-runtime
When to use which
| Use st-core when… | Use ProvChart when… |
|---|---|
| You want full control in FSCSS/CSS source | Data is JSON from backends, CI, or agents |
| Eight points (or st-core’s patterns) fit the story | Series length and count change per view |
| You’re deep in the FSCSS module ecosystem | You want API keys, quotas, SVG export, dashboard builder |
| Maximum clarity for teaching pure CSS charts | Production multi-series, combo, gauge, automation |
Many teams will use both: st-core for crafted marketing cards; ProvChart when the dataset refuses to stay eight samples long.
st-core.fscss proved a viral, high-performance idea—browser-native paint, minimal JS, no chart-library tax. ProvChart keeps that idea and removes the fixed-slot ceiling so the same belief works when the numbers come from a database, not a mixin call.
Top comments (0)