DEV Community

Cover image for Plot charts in pure CSS with st-core@v2
FSCSS tutorial for FSCSS tutorial

Posted on

Plot charts in pure CSS with st-core@v2

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

  1. Put your series in an FSCSS array.
  2. Normalize heights so “90” sits near the top of the chart.
  3. 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>
Enter fullscreen mode Exit fullscreen mode
@import((*) from st-core@v2)

@st-root()
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
<div class="chart">
  <div class="chart-fill"></div>
  <div class="chart-line"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

What happens:

  • @st-chart-points writes --st-p1…n as num(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%
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
<div class="chart">
  <div class="chart-fill"></div>
  <div class="chart-line"></div>
  <div class="chart-dot"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)