Syntax Reference
Complete reference for Lass syntax elements.
Quick Reference
| Syntax | Purpose | Example |
|---|---|---|
--- |
Zone separator | JS preamble before, CSS after |
{{ expr }} |
Expression interpolation | width: {{ x * 10 }}px; |
@(prop) |
Style lookup | outline: @(border); |
$name |
Variable substitution | color: $primary; |
@{ css } |
Style block | {{ @{ color: red; } }} |
// |
Single-line comment | // stripped from output |
Zone Separator (---)
A .lass file is split into two zones by opening and closing --- delimiters:
- Opening
---(line 1): Marks the start of JS preamble - Closing
---: Marks the end of preamble; everything after is CSS zone - Between delimiters: JavaScript (imports, variables, functions)
- After closing delimiter: CSS zone with Lass syntax extensions
If there's no opening --- on line 1, the entire file is CSS zone - plain CSS works as-is.
With preamble
---
const $color = 'blue'
---
p {
color: $color;
}
p {
color: blue;
}
Without preamble (pure CSS)
p {
color: red;
}
p {
color: red;
}
Functions in preamble
---
const space = (n) => `${n * 0.25}rem`;
---
.card {
padding: {{ space(4) }} {{ space(6) }};
gap: {{ space(2) }};
}
.card {
padding: 1rem 1.5rem;
gap: 0.5rem;
}
Expression Interpolation ({{ }})
{{ }} evaluates a JS expression and inserts the result into CSS. Works in value, selector, and property name positions.
Value position
---
const gap = 23
---
.box {
padding: {{ gap * 2 }}px;
}
.box {
padding: 46px;
}
Selector position
---
const tag = 'article'
---
{{ tag }} {
display: block;
}
article {
display: block;
}
Property name position
---
const prop = 'background-color'
---
.box {
{{ prop }}: blue;
}
.box {
background-color: blue;
}
Function calls
---
const fluid = (min, max) => `clamp(${min}rem, 5vw, ${max}rem)`;
---
.title {
font-size: {{ fluid(1.5, 3) }};
}
.title {
font-size: clamp(1.5rem, 5vw, 3rem);
}
Array auto-join
Arrays are automatically joined with space (CSS-friendly for shorthand properties):
---
const items = ['a', 'b', 'c']
---
.list {
--items: {{ items.map(x => x.toUpperCase()) }};
}
.list {
--items: A B C;
}
For different separators, use explicit .join():
---
const stops = ['#ff6b6b', '#feca57', '#48dbfb', '#ff9ff3'];
---
.gradient {
background: linear-gradient(90deg, {{ stops.join(', ') }});
}
.gradient {
background: linear-gradient(90deg, #ff6b6b, #feca57, #48dbfb, #ff9ff3);
}
Null/undefined handling
null, undefined, and false produce empty string - just like JSX conditional rendering:
---
const isLarge = false;
const isDisabled = true;
---
.button {
{{ isLarge && @{ padding: 1.5rem 2rem; } }}
{{ isDisabled && @{ opacity: 0.5; pointer-events: none; } }}
}
.button {
opacity: 0.5; pointer-events: none;
}
Style Lookup (@(prop))
In the CSS zone, read the last-declared value of a CSS property. Use it in value positions - resolution walks up the selector tree.
Basic usage
.box {
border: 1px solid black;
outline: @(border);
}
.box {
border: 1px solid black;
outline: 1px solid black;
}
Parent walk-up
.card {
padding: 1.5rem;
.content {
margin: @(padding);
}
}
.card {
padding: 1.5rem;
.content {
margin: 1.5rem;
}
}
Custom properties
@(--custom) works but var(--custom) is usually better (browser-resolved, supports fallbacks). Use @() for custom properties when you need build-time resolution:
.box {
--base-size: 16px;
padding: @(--base-size);
margin: var(--base-size);
}
.box {
--base-size: 16px;
padding: 16px;
margin: var(--base-size);
}
Inside expressions
---
const double = (v) => parseFloat(v) * 2 + 'px';
---
.box {
padding: 16px;
margin: {{ double(@(padding)) }};
}
.box {
padding: 16px;
margin: 32px;
}
Note: parseFloat('16px') returns 16 - JavaScript parses the leading number.
Unresolved lookups
Properties not found are preserved (for PostCSS or other tools):
.box {
color: @(font-size);
}
.box {
color: @(font-size);
}
Variable Substitution ($name)
Simple text substitution from $-prefixed variables. No expression evaluation.
Basic substitution
---
const $color = 'red'
---
p {
color: $color;
}
p {
color: red;
}
In selectors
---
const $component = 'card'
---
.$component {
display: block;
}
.card {
display: block;
}
Text-only (no evaluation)
---
const $gap = 23
---
.box {
padding: $gap * 2;
}
.box {
padding: 23 * 2;
}
Use {{ $gap * 2 }} for evaluated math.
Inside calc() (text substitution)
$param substitutes text, so the value goes into CSS calc():
---
const $gap = 23
---
.box {
padding: calc($gap * 1px);
}
.box {
padding: calc(23 * 1px);
}
The browser evaluates calc(23 * 1px) = 23px. For build-time math, use {{ }}.
Special values
| Value | Output | Why |
|---|---|---|
null |
unset |
CSS fallback |
undefined |
$name (preserved) |
No silent empty |
| non-existent | $name (preserved) |
Graceful degradation |
Protected in strings
$name inside quotes is literal text:
---
const $color = 'red'
---
.quote {
content: "the value is $color";
}
.quote {
content: "the value is $color";
}
Style Blocks (@{ })
Create CSS strings from within JS expressions. The inverse of {{ }}.
Basic style block
---
const makeBorder = () => @{ border: 1px solid; }
---
.box {
{{ makeBorder() }}
}
.box {
border: 1px solid;
}
With expressions inside
{{ }} inside @{ } enables dynamic values within generated blocks:
---
const colors = { primary: '#6366f1', secondary: '#8b5cf6' };
---
{{ Object.keys(colors).map(v => @{
.btn-{{ v }} {
background: {{ colors[v] }};
}
}) }}
.btn-primary {
background: #6366f1;
}
.btn-secondary {
background: #8b5cf6;
}
Generating utilities
Use {{ }} with template literals for dynamic CSS generation:
---
const sizes = [1, 2, 4, 8]
---
{{ sizes.map(n => `.m-${n} { margin: ${n * 0.25}rem; }`).join('\n') }}
.m-1 { margin: 0.25rem; }
.m-2 { margin: 0.5rem; }
.m-4 { margin: 1rem; }
.m-8 { margin: 2rem; }
Mixin pattern
---
function card(bg) {
return @{
background: {{ bg }};
border-radius: 8px;
padding: 16px;
}
}
---
.card {
{{ card('#ffffff') }}
}
.card {
background: #ffffff;
border-radius: 8px;
padding: 16px;
}
Conditional pattern (replaces @if/@else)
---
const darkMode = true
---
body {
{{ darkMode ? @{
background: #1a1a1a;
color: #e0e0e0;
} : @{
background: #ffffff;
color: #333333;
} }}
}
body {
background: #1a1a1a;
color: #e0e0e0;
}
Loop pattern (replaces @each/@for)
---
const breakpoints = { sm: '640px', md: '768px' }
---
{{ Object.entries(breakpoints).map(([name, width]) => @{
@media (min-width: {{ width }}) {
.container-{{ name }} {
max-width: {{ width }};
}
}
}) }}
@media (min-width: 640px) {
.container-sm {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container-md {
max-width: 768px;
}
}
Comments (//)
Use // for inline comments in the CSS zone - they're stripped from output, just like SCSS or Less. Standard CSS /* */ comments are preserved.
Single-line stripped
p {
// this comment is stripped
color: red;
}
p {
color: red;
}
Inline stripped
p {
color: red; // this is stripped
}
p {
color: red;
}
CSS comments preserved
/* preserved */
p {
color: red; /* also preserved */
}
/* preserved */
p {
color: red; /* also preserved */
}
Protected in strings and URLs
a {
content: "https://example.com";
}
.bg {
background: url(https://example.com/image.png);
}
a {
content: "https://example.com";
}
.bg {
background: url(https://example.com/image.png);
}
Both pass through unchanged - // inside strings and URLs is not a comment.