FSCSS Documentation
Figured Shorthand CSS is a lightweight CSS preprocessor that reduces repetition through shorthand syntax, reusable definitions, pattern matching, and conditional logic, while compiling to plain, dependency-free CSS.
Get StartedWhy FSCSS
FSCSS is designed to reduce repetitive declarations, keep stylesheets maintainable at scale, and add capabilities that plain CSS does not offer natively, such as conditional logic, reusable parameterized blocks, and array-driven generation. It compiles to standard CSS with no runtime dependency required for the compiled output.
Shorthand Syntax
Write fewer characters for common patterns such as shared property values and vendor prefixes.
Reusable Definitions
Define styles once with @define, @fun, or pattern(), then reuse them across a project.
Conditional Logic
Use @event to return different values based on parameters and comparisons.
Installation
NPM
Install FSCSS as a project dependency, or globally for CLI access.
npm install fscss@latest
# Global CLI installation
npm install -g fscss
fscss input.fscss output.css
CDN (Runtime Mode)
Load the FSCSS runtime directly in an HTML file for prototyping. Runtime mode compiles FSCSS in the browser and is not recommended for production.
<script src="https://cdn.jsdelivr.net/npm/fscss@1.1.25/exec.min.js" defer></script>
<link type="text/fscss" href="style.fscss">
CLI Compilation
For production, compile FSCSS to a standard CSS file ahead of deployment. This removes any runtime dependency from the shipped page.
fscss style.fscss style.css
Quick Start
Create a file named style.fscss:
$primary-color: #2563eb;
$border-radius: 8px;
.btn {
background: $primary-color;
color: white;
padding: 12px 24px;
border-radius: $border-radius;
transition: all 0.3s ease;
}
Compile it with the CLI:
fscss style.fscss style.css
Variables
FSCSS variables store and reuse values. They may be inline, globally scoped, locally scoped to a selector, or defined as a named block of properties. All variables compile down to native CSS --custom-properties or are inlined directly, depending on usage.
Inline and Global Variables
Declared with $name: value; and referenced with $name!. The ! forces evaluation at the point of use.
$primary: #3b82f6;
$secondary: #8b5cf6;
$global-font: 'Inter', sans-serif;
.button {
background: $primary!;
border: 2px solid $secondary!;
font-family: $global-font!;
}
Local (Scoped) Variables
A variable declared inside a selector is scoped to that block only.
.card {
$local-bg: #f1f5f9;
background: $local-bg!;
padding: 1rem;
}
Block Variables — str()
str(name, "css properties") stores a reusable block of CSS text under a label. Writing the bare label inside a selector injects the stored block. This is the primary mechanism for reusable style patterns that are simpler than a parameterized @define.
str(cardStyle, "
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
background: white;
transition: transform 0.3s ease;
")
str(cardHover, "
transform: translateY(-5px);
box-shadow: 0 10px 15px rgba(0,0,0,0.1);
")
.product-card {
cardStyle
max-width: 300px;
&:hover {
cardHover
}
}
.user-profile {
cardStyle
background: #f0f9ff;
}
Reusable Blocks — @define
@define creates a reusable, parameterized style definition. Parameters accept default values and must be accessed inside the body with @use(parameter). Direct references such as background: bg; are not resolved.
Syntax
@define name(param: default) {
property: @use(param);
}
Property Defines
Generate a group of declarations inside a rule.
@define card(bg: black, color: white, pad: 20px, bd-r: 10px) {
background: @use(bg);
border-radius: @use(bd-r);
padding: @use(pad);
color: @use(color);
}
.box {
@card(#007999)
}
Block Defines
Generate full CSS structures, such as media queries, using a string block enclosed in backticks.
@define mobile(size) {
`
@media (max-width: @use(size)) {
.container {
padding: 10px;
}
}
`
}
@mobile(768px)
Composition
Multiple defines may be applied within a single rule to build up a component from smaller, focused pieces.
@define btn-base(pad: 0.75rem 1.5rem) {
padding: @use(pad);
border: none;
border-radius: 6px;
cursor: pointer;
}
@define btn-primary(bg: #3b82f6) {
background: @use(bg);
color: white;
}
.primary-btn {
@btn-base()
@btn-primary()
}
Pattern Matching — pattern()
pattern() matches a natural-language description against phrases written elsewhere in a stylesheet and injects the associated CSS when the similarity score meets a defined threshold. Unlike @define, which requires an exact call, pattern() resolves by approximate meaning rather than an exact name.
Syntax
pattern(threshold: "description", `
css
`)
The threshold is a number from 0 to 1 representing the minimum similarity score required to trigger the pattern. It defaults to 1 (near-exact match) if omitted.
Example
pattern(0.5: "Hello World card", `
border-radius: 12px;
padding: 24px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: #fff;
`)
.card {
hello world card
}
Property Patterns and Block Patterns
A property pattern resolves inside a selector, contributing a set of declarations. A block pattern resolves to a full structure, such as a keyframe animation, and may be written standalone, outside any selector.
pattern(0.7: "animated keyframe for spin", `
@keyframes spin {
0% { transform: rotate(0); }
100% { transform: rotate(360deg); }
}
`)
an Animated keyframe for spin
Availability: pattern() is available from v1.1.25 via CDN, API, and CLI.
Function Stores — @fun
@fun(name){...} defines a named group of key-value pairs, referenced by dot notation. It is suited to design tokens such as spacing scales or color palettes.
Full Block Use
@fun(card-style){
background: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
}
.card {
@fun.card-style
border: 1px solid #e2e8f0;
}
Property and Value Access
@fun.name.property pulls a single property from the block. @fun.name.property.value resolves to only the value of that property, useful when the value is needed inline, such as inside linear-gradient().
@fun(e){
a: 100px;
b: 200px;
}
@fun(col){
1: #550066;
2: #005523;
}
.box {
width: @fun.e.a.value;
height: @fun.e.b.value;
background: linear-gradient(@fun.col.1.value, @fun.col.2.value);
}
Arrays — @arr
FSCSS arrays store ordered collections of values, referenced by index or iterated automatically. Arrays are 1-indexed and are string-native: every method returns a string directly rather than an array object, and method calls do not chain.
Declaration and Access
@arr(colors[#3b82f6, #8b5cf6, #06b6d4, #10b981]);
@arr(spacing[0.5rem, 1rem, 1.5rem, 2rem]);
.primary-button {
background: @arr.colors[1];
padding: @arr.spacing[2] @arr.spacing[3];
}
Auto-Indexing
Empty brackets @arr.name[] iterate through every item, generating one rule per item when used in a selector such as :nth-child().
@arr(delays[0.1s, 0.3s, 0.5s]);
@arr(colors[#ef4444, #f59e0b, #10b981]);
@arr(indexes[count(3, 1)]);
.loading-dot:nth-child(@arr.indexes[]) {
$index: @arr.indexes[];
animation-delay: @arr.delays[$index!];
background: @arr.colors[$index!];
}
Method Access Mode
Appending ! to an array reference switches from direct output to method access mode, enabling the methods below.
@arr.name /* direct output: item1, item2, item3 */
@arr.name!.method /* method access mode */
| Method | Description | Example |
|---|---|---|
.length | Number of items | @arr.n!.length → 3 |
.first | First item | @arr.n!.first |
.last | Last item | @arr.n!.last |
.list | Comma-separated list of all items | @arr.n!.list |
.join(sep) | Join items with a custom separator | @arr.n!.join(+) |
.reverse | Items in reverse order | @arr.n!.reverse |
.shuffle | Items in random order | @arr.n!.shuffle |
.sort | Alphabetic or numeric sort | @arr.n!.sort |
.unique | Remove duplicate items | @arr.n!.unique |
.indices | 1-based index list | @arr.n!.indices → 1,2,3 |
.randint | One random item | @arr.n!.randint |
.segment | Wrap each item in brackets | @arr.n!.segment |
.sum | Sum of numeric items | @arr.num!.sum |
.min | Minimum numeric value | @arr.num!.min |
.max | Maximum numeric value | @arr.num!.max |
.unit(val) | Append a unit to each item | @arr.n!.unit(px) |
.prefix(v) | Prepend a prefix to each item | @arr.b!.prefix(btn-) |
.surround(b,a) | Wrap each item with a before and after value | @arr.b!.surround([,]) |
Mutation
Items may be added or removed from a declared array using index-based operators.
@arr.name!+[item4, item5] /* append items */
@arr.name!-[2] /* remove the second item */
Limitations
Array methods do not chain; each method call returns a plain string, not a further array. Nested array declarations are not resolved: writing @arr.b[@arr.a!.list] stores the literal text rather than executing the inner array.
Combining with @random
@arr(backgrounds[#3b82f6, #8b5cf6, #06b6d4]);
.dynamic-card {
background: @random(@arr.backgrounds);
}
Random — @random
@random() selects one value from a list at compile time (or at each render, in runtime mode). It accepts either an inline list or a reference to a declared array.
Inline List
.btn {
background: @random([red, blue, green, brown]);
transform: translate(@random([10, 30, 60])px);
rotate: @random([0, 90, 150])deg;
}
From a Declared Array
@arr(palette[#4361ee, #f72585, #7209b7]);
.btn {
border: 2px groove @random(@arr.palette);
}
Each compilation, or each page render in runtime mode, selects a new value independently per @random() call.
Event Logic — @event
@event defines a parameterized function that returns different values based on conditions, using if, el-if, and el blocks. It brings conditional logic into a stylesheet without a build script.
Basic Usage
@event theme(mode) {
if mode: dark {
return: #111111;
}
el {
return: #ffffff;
}
}
body {
background: @event.theme(dark);
color: @event.theme(light);
}
Multiple Conditions
@event size(type) {
if type: small {
return: 12px;
}
el-if type: medium {
return: 16px;
}
el-if type: large {
return: 20px;
}
el {
return: 24px;
}
}
h1 {
font-size: @event.size(large);
}
Comparison Operators
Numeric parameters support ==, >, <, >=, and <=.
@event rating(score) {
if score >= 90 {
return: #10b981;
}
el-if score >= 70 {
return: #f59e0b;
}
el {
return: #ef4444;
}
}
.user-score-95 {
color: @event.rating(95);
}
Combining with num()
@event spacing(level) {
if level == 1 {
return: num(4*2)px;
}
el-if level == 2 {
return: num(8*2)px;
}
el {
return: num(16*2)px;
}
}
.card-medium {
padding: @event.spacing(2);
}
Combining with Variables
$dark-bg: #111111;
$light-bg: #ffffff;
@event themedBackground(mode) {
if mode: dark {
return: $dark-bg!;
}
el {
return: $light-bg!;
}
}
body {
background: @event.themedBackground(dark);
}
Best Practices
Give events clear, descriptive names such as theme, spacing, or device. Keep each event focused on a single responsibility rather than branching on multiple unrelated parameters.
Imports — @import
@import loads modules from a built-in library, a local path, or a remote URL, and can import selectively, with aliasing, or as a wildcard.
/* Named import */
@import((module) from lib-or-path)
/* Multiple named modules */
@import((
mod-a,
mod-b
) from lib-or-path)
/* Aliased import, avoids name clashes */
@import((module as alias) from lib-or-path)
/* Wildcard import */
@import((*) from lib-or-path)
/* Remote import from a CDN or absolute URL */
@import((module) from "https://cdn.example/file.fscss")
Example: Splitting Theme and Layout Files
@import(exec(_theme.fscss))
@import(exec(_layout.fscss))
:root {
@light-theme()
}
.container {
@container()
}
Attribute Selectors
The shorthand $(attribute:value) compiles to a standard CSS attribute selector.
$(type:submit) {
background: green;
color: white;
}
Keyframes Compact
$(@keyframes name, selectors, &[duration timing options]) defines a @keyframes rule and applies the resulting animation to the given selectors in a single block.
$(@keyframes slideIn, .box, .card, &[3s linear infinite]) {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
Vendor Prefixing
The -*- prefix expands a property across -webkit-, -moz-, -ms-, and -o-, followed by the unprefixed property.
.box {
-*-transform: rotate(45deg);
}
num()
num(expression) evaluates an arithmetic expression at compile time, supporting + - * /. A unit may be appended directly after the closing parenthesis.
selector {
max-height: num(40 * 4);
}
textarea {
max-height: num(@random([40, 10, 5, 0]) + 50);
}
count()
count(limit) generates a comma-separated sequence of numbers starting at 1. count(limit, step) starts from a custom step value.
exec(_log, "count(5)") /* 1, 2, 3, 4, 5 */
exec(_log, "count(10, 2)") /* 2, 4, 6, 8, 10 */
@arr num[count(5)]
div:nth-child(@arr.num[]) {
animation-delay: @arr.num[];
}
length()
length("text") returns the character count of a string, including spaces and punctuation. It is typically combined with num() to derive a size from a string's length.
.text-box {
width: num(length("Hello World") * 10)px;
}
copy()
copy(length, variable) extracts a substring from a value and stores it under a new variable name. A positive length counts from the start of the string; a negative length counts from the end. A length greater than the string returns the full string.
body {
background: #4ff000 copy(4, primary-color);
color: $primary-color!;
}
@ext()
@ext(source, start, length) slices a string by index and stores the result as a variable, referenced afterward as @ext.name.
body {
property: "the red color @ext(4,3: myRed)";
color: @ext.myRed;
}
rpt()
rpt(count, value) repeats a value a given number of times, most commonly used inside content for decorative or generated text.
.separator::after {
content: "rpt(10, '— ')";
}
exec()
exec(_log, message) and exec(_warn, message) print debugging output to the console at compile time. They do not produce CSS output.
exec(_log, 'compiling theme module')
exec(_warn, 'deprecated variable name')
File Structure Overview
| File | Purpose |
|---|---|
/exec.min.js | Minified runtime, served via the jsDelivr CDN, for browser-side compilation. |
/exec.js | Un-minified source of the runtime, used for development and debugging. |
/index.js | Standard npm package entry point. |
/xfscss.min.js | Minified module for handling FSCSS processing inside JavaScript environments. |
/e/ (v1.1.6+) | Error handling and logging utilities used for diagnostics during development. |
Project Background
FSCSS, short for Figured Shorthand Cascading Style Sheet, was conceived in 2022 by Ekuyik Sam, with early development carried out by Figsh. Initial development focused on the shared-value shorthand methods (%2 through %6, %i) and basic variable handling with $name: value.
In 2023, the copy() function was introduced alongside the first public test release. In 2024, the preprocessor's internal handling of variables and functions was expanded. In 2025, FSCSS was published to npm as the fscss package, adding an advanced string-replacement function, an extended %n() method supporting arbitrary counts, developer-facing console logging, and direct execution through a browser extension. In 2026, the project reached v1.1.25, adding array method extensions, @event conditional logic, and pattern() semantic matching.
FSCSS is published under Figsh Development as the fscss package on npm, and maintained through the fscss-ttr organization on GitHub.
Community
Contributions, bug reports, and feature suggestions are welcome through GitHub. Tutorials and discussion are also published on dev.to.