Full FSCSS can compile @define, arrays, pattern(), imports, and more. That’s powerful — and heavier than you need when all you want is size, mx / px, rounded, bg, stack, or inset: ratio-fit(16/9) in the browser.
So I shipped fscss-props: a properties-only runtime. It expands those shorthands at runtime and exposes a small DOM API. It does not parse advanced FSCSS features, so it stays fast even when the DOM is large.
Repo: github.com/Figsh/fscss-props
Shorthand map (same names as 1.2.5): fscss.devtem.org/shorthands
<script src="https://cdn.jsdelivr.net/npm/fscss-props@1.0.0/fscss-props.min.js"></script>
Load it early in <head> (no defer) if the first paint should already see expanded CSS.
What it does
| In CSS | In JS |
|---|---|
<style> blocks rewritten in place |
el.fscssProp.mx = '1rem' |
Inline style="size: 80px; …"
|
el.fscssProp.size = null removes owned longhands |
link[data-fscss] stylesheets |
el.fscssPropText = 'stack: 12px; p: 16px' |
.box {
mx: auto;
size: 200px 100px;
rounded-t: 12px;
center: true;
}
.vid {
inset: ratio-fit(16 / 9); /* parent: container-type: size */
}
Not included on purpose: @define, @arr, pattern(), module @import. For those, use the full FSCSS CLI/runtime and ship plain CSS.
Why this matters when you use JS
Utilities in pure CSS are fine until state drives the UI: theme, density, layout mode, open/closed. Then you either:
- toggle classes and keep a big CSS file, or
- set
element.stylewith longhands and remember every property you touched.
fscss-props sits in between:
-
Same names as design CSS —
px,rounded,stack, not onlypaddingLeft. -
Ownership — setting a shorthand applies longhands; setting it to
nullclears those longhands, not random inline noise. -
Bulk modes —
fscssPropTextreplaces the whole FSCSS-owned set (great forstackvscolsvshstackwithout leftoverdisplay/gap). - Small surface — no stylesheet AST for the whole language; property table + observers, so it’s aimed at large DOMs and frequent updates.
el.fscssProp.mx = '1rem';
el.fscssProp['ratio-fit'] = '16/9';
el.fscssProp.color = 'red'; // normal CSS works too
el.fscssProp.mx = null; // remove
console.log(el.fscssPropText);
Example: vanilla store → styles
A tiny subscribe store writes one fscssPropText blob whenever padding, radius, or theme changes:
<script src="https://cdn.jsdelivr.net/npm/fscss-props@1.0.0/fscss-props.min.js"></script>
<style>
body {
m: 0;
min-h: 100vh;
center: both;
p: 24px;
bg: #0b1020;
color: #e6edf7;
ff: system-ui, sans-serif;
}
.panel {
w: 100%;
max-w: 420px;
stack: 16px;
p: 24px;
bg: #121a30;
border: 1px solid #22304f;
rounded: 16px;
}
.preview {
min-h: 120px;
center: both;
fw: 600;
trans: all 0.15s ease;
}
/* …labels, range inputs, buttons… */
</style>
<div class="panel">
<div class="preview" id="box">Preview</div>
<label>Padding <input id="pad" type="range" min="8" max="40" value="16"></label>
<label>Radius <input id="rad" type="range" min="0" max="32" value="12"></label>
<div class="bar" id="themes">
<button type="button" data-bg="#6366f1" aria-pressed="true">Indigo</button>
<button type="button" data-bg="#0ea5e9">Sky</button>
<button type="button" data-bg="#10b981">Emerald</button>
</div>
<pre id="out"></pre>
</div>
<script>
function createStore(initial) {
let state = { ...initial };
const subs = new Set();
return {
get: () => state,
set(patch) {
state = { ...state, ...patch };
subs.forEach((fn) => fn(state));
},
subscribe(fn) {
subs.add(fn);
fn(state);
return () => subs.delete(fn);
},
};
}
const box = document.getElementById('box');
const out = document.getElementById('out');
const store = createStore({ pad: 16, radius: 12, bg: '#6366f1' });
store.subscribe((s) => {
box.fscssPropText = [
`p: ${s.pad}px`,
`rounded: ${s.radius}px`,
`bg: ${s.bg}`,
'color: #fff',
'center: true',
].join('; ');
out.textContent = box.fscssPropText.replace(/; /g, ';\n');
});
document.getElementById('pad').oninput = (e) =>
store.set({ pad: +e.target.value });
document.getElementById('rad').oninput = (e) =>
store.set({ radius: +e.target.value });
document.getElementById('themes').onclick = (e) => {
const btn = e.target.closest('button');
if (!btn) return;
[...e.currentTarget.children].forEach((b) =>
b.setAttribute('aria-pressed', b === btn));
store.set({ bg: btn.dataset.bg });
};
</script>
Full demos (shape lab, ratio-fit, reactive list): examples/ in the repo.
When to use which
| Need | Tool |
|---|---|
| Author modules, arrays, patterns; ship static CSS | FSCSS CLI / full runtime |
| Live property shorthands + JS control | fscss-props |
| Max performance in production, no live expand | Compile with CLI; omit the script |
Links
- GitHub: Figsh/fscss-props
- npm: fscss-props
- Shorthands reference: fscss.devtem.org/shorthands
- FSCSS core: Figsh/xfscss
MIT
Top comments (0)