DEV Community

Cover image for How to build an FSCSS module like st-core.fscss
FSCSS tutorial for FSCSS tutorial

Posted on

How to build an FSCSS module like st-core.fscss

To build an FSCSS module like st-core.fscss, create a stylesheet of reusable @define mixins, expose a small public API, and compile/test it with the FSCSS CLI. st-core follows this pattern for design tokens, layout helpers, charts, stat cards, and progress bars.

1. Learn the module structure

An FSCSS module is a stylesheet of reusable style definitions that other FSCSS files can import.

A practical module should contain:

  • Private design tokens and helper logic
  • Public mixins with a consistent naming convention
  • Parameter defaults
  • Selector arguments so consumers can choose their own class names
  • Documentation and examples
  • A compiled CSS output for production

Typical project layout:

my-core.fscss
README.md
examples/
  demo.fscss
dist/
  my-core.css
package.json
Enter fullscreen mode Exit fullscreen mode

2. Define a public API

Use a namespace to avoid collisions. Parameters must be read with @use(). For full rules, wrap the body in a backtick string block and target the selector with @use(selector){ ... }.

@define mc-root(root:root){`
:@use(root){
  --mc-bg: #10131a;
  --mc-surface: #191e28;
  --mc-card: #222938;
  --mc-accent: #4f9cff;
  --mc-text: #eef4ff;
  --mc-muted: #8491a7;
  --mc-radius: 12px;
  --mc-space: 20px;
  --mc-chart-line-width: 1.5px;
}
`}
Enter fullscreen mode Exit fullscreen mode

Verify the exact syntax against the FSCSS version you ship against. st-core.fscss requires FSCSS 1.1.24 or later.

3. Build reusable mixins

A mixin should generate one coherent component or behavior.

Container

@define mc-container(st:body){`
  @use(st){
    width: 100%;
    max-width: 1100px;
    margin-inline: auto;
    padding: var(--mc-space);
  }
`}
Enter fullscreen mode Exit fullscreen mode

Card

@define mc-card(st:.mc-card){`
  @use(st){
    background: var(--mc-card);
    border: 1px solid color-mix(in srgb, var(--mc-accent) 18%, transparent);
    border-radius: var(--mc-radius);
    padding: var(--mc-space);
    color: var(--mc-text);
  }
`}
Enter fullscreen mode Exit fullscreen mode

Progress bar

@define mc-progress(st:.mc-progress, default-width: 0%){`
  @use(st){
    width: var(--mc-progress-width, @use(default-width));
    height: 8px;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--mc-accent), #9bc7ff);
    transition: width 400ms ease;
  }
`}
Enter fullscreen mode Exit fullscreen mode

The mixin establishes structure; CSS custom properties let individual instances override values.

4. Create a chart data mixin

st-core stores chart values in custom properties (--st-p1 … --st-p8). The fill, line, and dots inherit them. Values are supplied on a natural 0–100 scale (100 = top); the mixin inverts them for CSS coordinates.

@define mc-chart-points(
  p1: 0, p2: 0, p3: 0, p4: 0,
  p5: 0, p6: 0, p7: 0, p8: 0
){
  --mc-p1: num(100 - @use(p1))%;
  --mc-p2: num(100 - @use(p2))%;
  --mc-p3: num(100 - @use(p3))%;
  --mc-p4: num(100 - @use(p4))%;
  --mc-p5: num(100 - @use(p5))%;
  --mc-p6: num(100 - @use(p6))%;
  --mc-p7: num(100 - @use(p7))%;
  --mc-p8: num(100 - @use(p8))%;
}
Enter fullscreen mode Exit fullscreen mode

5. Render the chart with CSS

Use clip-path: polygon(). This is pure CSS—no SVG, canvas, or JS charting library.

Area fill

@define mc-chart-fill(st:.mc-chart-fill){`
  @use(st){
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--mc-accent) 35%, transparent);
    clip-path: polygon(
      0% var(--mc-p1),
      14% var(--mc-p2),
      28% var(--mc-p3),
      42% var(--mc-p4),
      57% var(--mc-p5),
      71% var(--mc-p6),
      85% var(--mc-p7),
      100% var(--mc-p8),
      100% 100%,
      0% 100%
    );
  }
`}
Enter fullscreen mode Exit fullscreen mode

Line stroke (thin polygon with return path)

@define mc-chart-line(st:.mc-chart-line){`
  @use(st){
    position: absolute;
    inset: 0;
    background: var(--mc-accent);
    filter: drop-shadow(0 0 6px var(--mc-accent));
    clip-path: polygon(
      0% var(--mc-p1),
      14% var(--mc-p2),
      28% var(--mc-p3),
      42% var(--mc-p4),
      57% var(--mc-p5),
      71% var(--mc-p6),
      85% var(--mc-p7),
      100% var(--mc-p8),
      100% calc(var(--mc-p8) + var(--mc-chart-line-width)),
      85% calc(var(--mc-p7) + var(--mc-chart-line-width)),
      71% calc(var(--mc-p6) + var(--mc-chart-line-width)),
      57% calc(var(--mc-p5) + var(--mc-chart-line-width)),
      42% calc(var(--mc-p4) + var(--mc-chart-line-width)),
      28% calc(var(--mc-p3) + var(--mc-chart-line-width)),
      14% calc(var(--mc-p2) + var(--mc-chart-line-width)),
      0% calc(var(--mc-p1) + var(--mc-chart-line-width))
    );
  }
`}
Enter fullscreen mode Exit fullscreen mode

For production-quality curves you may prefer SVG or canvas; the CSS technique is ideal for small, fixed-point visualizations.

6. Import and use the module

@import((*) from my-core)

@mc-root()
@mc-card(.stat-card)
@mc-chart-fill(.chart-fill)
@mc-chart-line(.chart-line)

.chart {
  position: relative;
  width: 100%;
  height: 220px;
  overflow: hidden;
  background: var(--mc-surface);
  border-radius: var(--mc-radius);

  @mc-chart-points(20, 35, 33, 30, 48, 35, 66, 37)
}

.chart-line {
  --mc-accent: #55d6be;
}
Enter fullscreen mode Exit fullscreen mode

Corresponding HTML:

<div class="stat-card">
  <strong>Revenue</strong>
  <span>$84,201</span>
</div>

<div class="chart">
  <div class="chart-fill"></div>
  <div class="chart-line"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

st-core uses the same consumer pattern: import the module, call the component mixins, then place the generated classes in HTML.

7. Support runtime updates

Because data lives in custom properties, JavaScript only needs to write variables:

function updateChart(chart, values) {
  values.forEach((value, index) => {
    chart.style.setProperty(
      `--mc-p${index + 1}`,
      `${100 - value}%`
    );
  });
}

updateChart(document.querySelector('.chart'), [
  40, 75, 60, 45, 80, 50, 70, 30
]);
Enter fullscreen mode Exit fullscreen mode

JavaScript supplies data; CSS remains responsible for rendering.

8. Compile and test

npm install -g fscss
fscss examples/demo.fscss dist/demo.css
Enter fullscreen mode Exit fullscreen mode

Test at least:

  • Mixins with default arguments
  • Mixins with custom selectors
  • Multiple component instances
  • Missing or invalid values
  • Responsive widths
  • CSS custom-property overrides
  • Compiled output in browsers (no FSCSS runtime)

For production, compile the module into CSS. st-core documents CDN/runtime mode for prototypes and compiled mode for deployment.

Recommended API design

@mc-root()
@mc-container(selector)
@mc-card(selector)
@mc-chart-points(values...)
@mc-chart-fill(selector)
@mc-chart-line(selector)
@mc-chart-grid(selector, rows, columns)
@mc-progress(selector, default-width)
Enter fullscreen mode Exit fullscreen mode

Keep implementation details private, prefix every generated variable and class, document every parameter, and provide one minimal example per mixin. That combination is what makes a module like st-core.fscss reusable rather than merely a collection of CSS snippets.


Sources

Top comments (0)