Ship real UI from role-based phrases. No hand-rolled component sheets. FSCSS pattern() matches what you type, injects CSS, and lets you pass colors, sizes, and durations in the same line.
FSCSS version 1.2.4+
The idea
You register a short description and a CSS template once. Elsewhere in the stylesheet you write a normal-looking phrase. If the phrase scores high enough against that description, the template is expanded in place.
/* Library */
pattern(0.65: "solid filled primary button", `
background: var(--pattern-accent);
background: @match((?:bg|background):\s*([#\w().%-]+));
color: var(--pattern-text-on-accent);
/* … */
`)
/* App */
.btn-primary {
solid filled primary button bg: #344466
}
No mixin name to remember. No build step required if you use the browser runtime. For production, compile once with the CLI (or a GitHub Action) and ship plain CSS.
Feature overview
1. Role-based descriptions
Patterns are named by role, not by a fixed color:
| Good | Avoid |
|---|---|
solid filled primary button |
solid purple primary button with white label |
ghost outline button with accent border |
blue outline button |
Colors live in design tokens (pattern-root) or arrive through @match when the caller supplies them. Role words stay stable when your palette changes; color words in the description go stale and pollute matching.
2. @match — parameters in the phrase
Inside a template, @match(regex) runs on the caller’s line. First capturing group wins.
background: var(--pattern-accent);
background: @match((?:bg|background):\s*([#\w().%-]+));
| Phrase | Result |
|---|---|
solid filled primary button |
default token |
solid filled primary button bg: #0ea5e9 |
override #0ea5e9
|
Labels used across the module include: bg / background, color / text / label, from / to, border, radius, lift, scale, duration / time, size, ring, min / max / pad / gap, thumb, fill, lines, and more.
Regexes stay simple ([#\w().%-]+ for colors, length patterns for sizes). FSCSS 1.2.4 rejects hostile nested quantifiers before compile.
3. Cascade fallbacks (no empty custom properties)
When a match fails, the engine can emit an empty value. A declared-but-empty custom property blocks the fallback:
/* Broken pattern */
--_bg: ; /* defined but empty */
background: var(--_bg, var(--pattern-accent)); /* fallback never used */
This library uses the cascade instead:
background: var(--pattern-accent); /* always present */
background: @match(...); /* empty → invalid → discarded */
Defaults always work. Overrides only apply when the phrase supplies a value.
4. Shared threshold
@define patterns(thr:0.65){`
pattern(@use(thr): "…", `…`)
`}
@patterns(0.4) /* caller sets sensitivity for the whole pack */
| Threshold | Feel | Typical use |
|---|---|---|
| 0.7–0.9 | Strict | Short component names |
| 0.4–0.5 | Forgiving | Demo / longer role phrases |
| ≤ 0.35 | Very open | Easy collisions — keep descriptions unique |
The demo uses 0.4 so paraphrases still hit without being reckless.
5. Design tokens in one place
@pattern-root() /* writes --pattern-accent, radii, shadows, pads, … on :root */
Patterns reference tokens; callers override only what they need in the phrase.
6. Namespaced motion
Keyframes are prefixed (patternFadeInUp, patternShimmer, …) so they do not collide when you import other FSCSS modules.
What’s in the library
-
Cards — two-tone gradient (
from/to/color), soft elevated surface (bg/radius) -
Buttons — solid primary, surface, ghost outline (
bg/color/border) -
Hover — lift, scale, glow (
lift/scale/glow) -
Motion — fade-in-up, pulse, spin, shimmer + keyframes (
duration) - Layout — centered max-width container, auto-fit grid, sticky translucent header, divider
- Components — input + focus ring, pill badge, glass, tooltip, avatar + ring, skeleton, toggle track, progress track/fill, status alerts, tab underline, thin scrollbar
-
Text — single-line truncate, multiline clamp (
lines) -
A11y —
:focus-visiblering, disabled muted state
Usage
Install
npm install fscss@1.2.4
Local import
@import((pattern-root, patterns) from "./patterns.fscss")
@pattern-root()
@patterns(0.4)
.hero {
vibrant two tone gradient card from: #121212 to: #676756 color: #fff
}
.btn-primary {
solid filled primary button bg: #344466
}
.btn-primary {
lift up on hover with stronger shadow lift: -2px
}
Compile (CLI or CI)
fscss demo.fscss demo.css
The repo includes a GitHub Action that recompiles demo.css on every .fscss push. The live demo serves that compiled file — no runtime required on the page.
Browser runtime (playgrounds)
<link type="fscss" href="./demo.fscss">
<script src="https://cdn.jsdelivr.net/npm/fscss@1.2.4/runtime.min.js" defer></script>
Design choices worth knowing
- Descriptions are stable contracts. Change tokens freely; do not bake hex values into the match text.
- One match group per concern. Keep regexes readable and 1.2.4-safe.
- Defaults first, overrides second. That is the only reliable way to survive failed matches today.
- Project-specific patterns belong in your own module. This pack is for roles many apps share.
Contribute
PRs welcome. Prefer:
- Role-based, lexically distinct descriptions
- Cascade defaults + simple
@match - Namespaced
pattern*keyframes - A short note in the PR with example phrases and the CSS they produce
See CONTRIBUTING.md.
Links
Write the role. Optionally pass the values in the same line. Get real CSS.
Top comments (0)