Simple CSS

SCS stands for Simple Cascading Style. It is a thin compile-time layer over css — compile-time variables, design tokens, reusable styles, functions, components, nesting, responsive blocks, loops and explicit ordering — compiled to plain css by MMO\client\SCSCompiler.

Everything on this page, and on The language and PHP bridge, was compiled by the real compiler while the page was being rendered: what you read as "generated css" is what your build would write.

The four rules

@xxx

a language instruction — it runs at compile time

$xxx

a compile-time variable — it is replaced, never emitted

xxx::yyy

a name another file declares — a file is a namespace

@theme

the only construct that produces runtime css variables

example.scs
@let $size = 16px; @theme { spacing {md: 16px;} } .card { width: $size; padding: $spacing.md; }
generated css
:root { --spacing-md: 16px; } .card { width: 16px; padding: var(--spacing-md); }

Pure php, no binary

Unlike the Sass pipeline, nothing is shelled out: the lexer, the parser, the evaluator and the emitter are php, so a stylesheet compiles anywhere the framework runs — no dart binary, no node, no build step to install.

.scs → Lexer → Parser → ast → evaluation (variables, @fn, php bridge) → style expansion → themes → nesting → @screen → @order → css

Compiling

use MMO\client\SCSCompiler; $css = SCSCompiler::Shared()->compileText($source)->css(); //or, from files -- compile() logs and writes a visible css error instead of throwing $result = SCSCompiler::Shared()->compileFile('{basepath}/styles/app.scs'); SCSCompiler::Shared()->compile('{basepath}/www/app.css', '{basepath}/styles/app.scs');

A compilation returns a Result: css(), but also phpValue('spacing') to read back a compile-time variable and themes() to read the design tokens the stylesheet declared. See PHP bridge.

A real stylesheet

The demo ships a small design system in demo/www/contents/scs/ — theme.scs (the tokens), forms.scs (the forms namespace: a style library) and app.scs (the components). This is app.scs, compiled from disk:

example.scs
@import "theme.scs"; @import "forms.scs"; @config @screen.tablet = 768px; @fn spacing($n) { return $n * 4px; } @style center { display: flex; align-items: center; justify-content: center; } @component button { @use center; padding: $spacing.sm $spacing.md; border-radius: $radius.md; background: $colors.primary; color: white; cursor: pointer; transition: background $theme::transition; &:hover {background: $colors.primary-dark;} @variant danger {background: $colors.danger;} @variant outline { background: transparent; color: $colors.primary; border: 1px solid $colors.primary; } @screen tablet {padding: $spacing.md $spacing.lg;} @order -1 {border: none;} } @component .field { @use forms::field; input {@use forms::input;} width: ${forms::double(120)}px; } @for $i in 1..4 { .mt-${$i} {margin-top: spacing($i);} }
generated css
:root { --colors-primary: #6366f1; --colors-primary-dark: #4f46e5; --colors-danger: #ef4444; --colors-surface: #ffffff; --colors-text: #111827; --colors-muted: #6b7280; --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 16px; --spacing-lg: 24px; --radius-sm: 4px; --radius-md: 8px; } [data-theme="dark"] { --colors-primary: #818cf8; --colors-surface: #111827; --colors-text: #f9fafb; } .button { border: none; display: flex; align-items: center; justify-content: center; padding: var(--spacing-sm) var(--spacing-md); border-radius: var(--radius-md); background: var(--colors-primary); color: white; cursor: pointer; transition: background 150ms ease; } .button:hover { background: var(--colors-primary-dark); } .button--danger { background: var(--colors-danger); } .button--outline { background: transparent; color: var(--colors-primary); border: 1px solid var(--colors-primary); } .field { display: flex; flex-direction: column; gap: 4px; width: 240px; } .field input { border: 1px solid var(--colors-muted); border-radius: var(--radius-sm); padding: var(--spacing-sm) var(--spacing-md); transition: border-color 150ms ease; } .mt-1 { margin-top: 4px; } .mt-2 { margin-top: 8px; } .mt-3 { margin-top: 12px; } .mt-4 { margin-top: 16px; } @media (min-width: 768px) { .button { padding: var(--spacing-md) var(--spacing-lg); } }
live preview

Options

Every option is a property of the module: set it in the application configuration, per compilation, or from the source itself with @config. These are the defaults this installation runs with.

OptionDefaultDescription
screens {"tablet":"481px","desktop":"981px","wide":"1707px"} named breakpoints used by @screen
paths ["phar:///homepages/38/d520689115/htdocs/mmoframework.fr/www/releases/3.1.4/packages/mmo.client.phar/src/styles"] extra directories searched by @import
componentSelector ".{name}" how @component button builds its selector
variantSelector "{selector}--{variant}" how @variant primary builds its selector
themeSelector "[data-theme=\"{name}\"]" how @theme dark builds its selector
varPrefix "" prefix of every generated custom property
indent " " indentation of the generated css
minify true compact output
optimize true merge consecutive identical selectors, drop overridden declarations
resolveThemeValues false inline the token values instead of var() — runtime theming stops working
strictFunctions true reject an unknown function instead of emitting it as css
allowImports true set to false to refuse every @import
sourceMap false write a .css.map next to the css (true) or inside it ('inline'), so devtools shows the .scs line
context [] the compilation context every compilation starts with

Where to go next

The language

every construct of the norm, side by side with the css it generates.

PHP bridge

php::, context::, the evaluation api and the trust boundary.

Playground

write your own .scs and compile it here.

Reference

the complete language reference, from docs/SimpleCSS.md.

Tags

<#simple css> <#scs> <#css> <#compiler>