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 Started

Why 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.

bash
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.

html
<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.

bash
fscss style.fscss style.css

Quick Start

Create a file named style.fscss:

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:

bash
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.

fscss
$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.

fscss
.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.

fscss
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

fscss
@define name(param: default) {
  property: @use(param);
}

Property Defines

Generate a group of declarations inside a rule.

fscss
@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.

fscss
@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.

fscss
@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

fscss
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

fscss
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.

fscss
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

fscss
@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().

fscss
@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

fscss
@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().

fscss
@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.

fscss
@arr.name        /* direct output: item1, item2, item3 */
@arr.name!.method  /* method access mode */
Method Description Example
.lengthNumber of items@arr.n!.length → 3
.firstFirst item@arr.n!.first
.lastLast item@arr.n!.last
.listComma-separated list of all items@arr.n!.list
.join(sep)Join items with a custom separator@arr.n!.join(+)
.reverseItems in reverse order@arr.n!.reverse
.shuffleItems in random order@arr.n!.shuffle
.sortAlphabetic or numeric sort@arr.n!.sort
.uniqueRemove duplicate items@arr.n!.unique
.indices1-based index list@arr.n!.indices → 1,2,3
.randintOne random item@arr.n!.randint
.segmentWrap each item in brackets@arr.n!.segment
.sumSum of numeric items@arr.num!.sum
.minMinimum numeric value@arr.num!.min
.maxMaximum 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.

fscss
@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

fscss
@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

fscss
.btn {
  background: @random([red, blue, green, brown]);
  transform: translate(@random([10, 30, 60])px);
  rotate: @random([0, 90, 150])deg;
}

From a Declared Array

fscss
@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

fscss
@event theme(mode) {
  if mode: dark {
    return: #111111;
  }
  el {
    return: #ffffff;
  }
}

body {
  background: @event.theme(dark);
  color: @event.theme(light);
}

Multiple Conditions

fscss
@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 <=.

fscss
@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()

fscss
@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

fscss
$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.

fscss
/* 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

fscss
@import(exec(_theme.fscss))
@import(exec(_layout.fscss))

:root {
  @light-theme()
}

.container {
  @container()
}

Shorthand Properties

Shared Values — %n

%2 through %6 assign a single value to two through six properties in one call. %i supports a custom count.

fscss
div {
  %2(width, height[: 50px;])
}

.box {
  %3(border-radius, outline-width, min-width[: 5px;])
}

mx() and mxs()

Both apply the same value to multiple properties. mxs() takes a plain shared value; mx() requires the colon and semicolon to be written explicitly, allowing more flexible declarations.

fscss
.card {
  mxs(width, height, max-height, max-width, min-width, min-height, '200px')
}
.box {
  mx(width, height, max-height, max-width, min-width, min-height, ': 200px;')
}

Attribute Selectors

The shorthand $(attribute:value) compiles to a standard CSS attribute selector.

fscss
$(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.

fscss
$(@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.

fscss
.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.

fscss
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.

fscss
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.

fscss
.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.

fscss
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.

fscss
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.

fscss
.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.

fscss
exec(_log, 'compiling theme module')
exec(_warn, 'deprecated variable name')

File Structure Overview

File Purpose
/exec.min.jsMinified runtime, served via the jsDelivr CDN, for browser-side compilation.
/exec.jsUn-minified source of the runtime, used for development and debugging.
/index.jsStandard npm package entry point.
/xfscss.min.jsMinified 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.