DEV Community

Cover image for patterns.fscss: Write CSS in Plain English
FSCSS for FSCSS tutorial

Posted on

patterns.fscss: Write CSS in Plain English

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

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().%-]+));
Enter fullscreen mode Exit fullscreen mode
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 */
Enter fullscreen mode Exit fullscreen mode

This library uses the cascade instead:

background: var(--pattern-accent);              /* always present */
background: @match(...);                        /* empty → invalid → discarded */
Enter fullscreen mode Exit fullscreen mode

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 */
Enter fullscreen mode Exit fullscreen mode
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 */
Enter fullscreen mode Exit fullscreen mode

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-visible ring, disabled muted state

Usage

Install

npm install fscss@1.2.4
Enter fullscreen mode Exit fullscreen mode

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

Compile (CLI or CI)

fscss demo.fscss demo.css
Enter fullscreen mode Exit fullscreen mode

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

Design choices worth knowing

  1. Descriptions are stable contracts. Change tokens freely; do not bake hex values into the match text.
  2. One match group per concern. Keep regexes readable and 1.2.4-safe.
  3. Defaults first, overrides second. That is the only reliable way to survive failed matches today.
  4. 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)