DEV Community

Cover image for Plain CSS builds for FSCSS modules — when yes, when no, and how
FSCSS tutorial for FSCSS tutorial

Posted on

Plain CSS builds for FSCSS modules — when yes, when no, and how

overlapui ships two products that look related but serve different users:

  1. overlapui.fscss — the real module: @defines, selective @import, token helpers, compose only what you need.
  2. overlapui.css / .min.css — a precompiled drop-in sheet for people who never touch FSCSS: link it, use the utility classes, ship.

That second path raises a fair question: can every other module in the ecosystem do the same?

Short answer: most can ship a plain CSS build; not all should treat it as the primary product. The limit is not “FSCSS can’t compile to CSS” — it always can. The limit is what you lose when you freeze a parameterized module into one fixed stylesheet.


What a “plain CSS version” actually is

It is not a second language implementation. It is:

module.standalone.fscss  →  (fscss CLI / CI)  →  module.css
Enter fullscreen mode Exit fullscreen mode

A standalone entry file typically:

  • @imports the full module (or a fixed subset)
  • calls the root / pack helpers with default selectors
  • sets default tokens on :root

CI or npm run build:css runs the official compiler and publishes module.css + module.min.css next to the .fscss source.

Users who only want CSS:

<link rel="stylesheet" href="…/module.min.css">
Enter fullscreen mode Exit fullscreen mode

Users who want FSCSS keep:

@import((avatar-overlap, ou-root) from overlapui)
@ou-root()
@avatar-overlap(.team)
Enter fullscreen mode Exit fullscreen mode

Same design system, two consumption modes.


When a plain CSS build fits well

Plain CSS shines when the module is already class-and-token shaped:

Pattern Why CSS export works
Fixed utility classes (.avatar-overlap, .circle-overlap) Markup contract is stable
Design tokens on :root (--ou-*, --ai-*) Consumers override variables without recompiling
Hover/focus motion in pure CSS No compile-time loop required at use site
“Drop in and look right” demos Docs and frameworks that only allow <link>

overlapui, large parts of form, icon-mask, circle-progress (default ring classes), and ai-ui (default .ai-shell / .ai-message shells) are in this family. A default compile gives a useful sheet; tokens still allow theming.


When a plain CSS build is weak or misleading

Some modules are generators, not fixed component packs.

st-core@v2 is the clearest case. Charts are driven by:

  • @arr data of variable length
  • compile-time loops that emit --st-p1 … --st-pN and clip-path polygons
  • helpers like @st-chart-line(.line, myData) where the array name and length are part of the compile

A single st-core.css can only encode one frozen default (e.g. eight points with fixed sample values). That is fine as a preview skin or empty chrome. It is not a replacement for “pass my sales array and get the right polygon.” For real data, you still need FSCSS at build time, or you write the CSS variables from JavaScript after a compile-time shell exists.

Similarly:

Situation Plain CSS limitation
Heavy @arr / rpt / count() expansion Defaults only; not your dataset
Selector parameters (@define x(sel:.foo)) Standalone must pick one selector set
Selective imports as the main API Full CSS file is the opposite of tree-shaking
pattern() libraries Matching is compile-time against phrases in your sheet

So: yes, you can always produce a .css artifact. For generator modules, market it as default theme / demo build, not “full module without FSCSS.”


Should all libraries ship one?

Recommended policy for the ecosystem:

  1. Ship .fscss first — source of truth, selective import, parameters.
  2. Ship .css when the default class API is useful alone (overlap, form chrome, icon masks, AI shell defaults).
  3. Document the gap when the CSS build cannot express the main feature (charts, data-driven loops).
  4. Keep CI honest — standalone.fscss → module.css on every release so CSS does not drift from source.

Not every repo needs npm style fields and jsDelivr min files on day one. Prioritize modules people already try to <link> without a preprocessor.


How to add a plain CSS build (any module)

1. Add module.standalone.fscss

@import((*) from "./module.fscss")

@module-root()
@module-pack()   /* or each public helper with default selectors */
Enter fullscreen mode Exit fullscreen mode

Use the same defaults the README teaches for copy-paste HTML.

2. Build in package scripts / GitHub Actions

fscss module.standalone.fscss module.css
# optional minify step → module.min.css
Enter fullscreen mode Exit fullscreen mode

Pin the FSCSS CLI version to the module’s fscss_version / engines field.

3. Publish

  • files: include .fscss, .standalone.fscss, .css, .min.css
  • style: point at the main CSS file for tooling that reads package metadata
  • README: two paths — “With FSCSS” and “CSS only”

4. Tell CSS-only users how to theme

If tokens are on :root, document:

:root {
  --mod-accent: #5eead4;
  --mod-radius: 12px;
}
Enter fullscreen mode Exit fullscreen mode

That recovers a large part of customization without reintroducing the compiler.


Tradeoffs to state clearly

FSCSS module Plain CSS build
Selective @import Whole default surface (or a chosen subset baked in)
@define(sel, n, data) Fixed selectors / fixed N
One source, many call sites One compiled snapshot
Ideal for design systems Ideal for HTML templates, CodePen, no-build sites

Neither path is “more correct.” overlapui.css exists so non-FSCSS pages still get the overlap behavior. overlapui.fscss exists so serious apps only emit the rules they call.


Practical answer

  • Can all modules have a plain CSS version?

    Technically yes — compile a standalone entry that invokes defaults.

  • Should all modules present that as equivalent to the FSCSS module?

    No. Class/token UI packs ≈ strong CSS builds. Data- and loop-driven modules ≈ CSS demos + FSCSS (or JS-written variables) for the real feature.

  • What should authors or anyone do?

    For example: form, icon-mask, circle-progress defaults, ai-ui shells, customizable page chrome: still good to follow the overlapui pattern (standalone file, CI, min.css, dual README). For st-core and similar: ship an optional default CSS for docs, and keep the array/compile path as the real API.

Plain CSS is a distribution channel, not a different product philosophy. FSCSS remains the authoring layer; .css is the frozen export for people who only need the defaults.

Top comments (0)