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:

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.


Back to Home | Getting Started