@(prop) Property Accessor

@(prop) lets you reference the value of a CSS property that's already been declared. Write @(border) and you get back whatever border was last set to in the current selector tree.

The explicit parentheses make the syntax unambiguous:

This is a compile-time feature - @(prop) resolves during transpilation, not at browser runtime. The transpiler uses string slice + lazy regex lookup to find and replace @(border) with the literal value.

How resolution works:

  1. Cut the CSS zone into slices at { and } boundaries
  2. When @(prop) is encountered, search current slice for prop: pattern
  3. Not found? Walk backward through parent slices (parent scopes)
  4. Extract the value and replace @(prop) with the literal value
  5. If not found at root of selector tree, preserve @(prop) unchanged

Why preserve instead of empty string? Lass plays nicely with other CSS tools. An unresolved @(prop) might be:

A few boundaries that @(prop) doesn't cross:

valid: same-block reference

@(border) resolves to the border value declared earlier in the same block.

.box {
  border: 1px solid black;
  outline: @(border);
}
.box {
  border: 1px solid black;
  outline: 1px solid black;
}

valid: parent walk-up

If the property isn't in the current block, resolution walks up to the parent. Here .child doesn't declare border, so @(border) finds it on .parent.

.parent {
  border: solid;
  .child {
    outline: @(border);
  }
}
.parent {
  border: solid;
  .child {
    outline: solid;
  }
}

valid: nearest ancestor wins

When multiple ancestors declare the same property, the closest one wins. Same idea as variable scoping in block-scoped languages - the inner declaration shadows the outer.

.parent {
  border: solid;
  .child {
    border: dashed;
    .grandchild {
      outline: @(border);
    }
  }
}
.parent {
  border: solid;
  .child {
    border: dashed;
    .grandchild {
      outline: dashed;
    }
  }
}

valid: last value before the reference

Within a single block, @(prop) picks up the last value declared before the reference. Declarations after it don't count.

.child {
  border: dashed;
  border-left: @(border);
}
.child {
  border: dashed;
  border-left: dashed;
}

valid: multiple declarations - last one wins

When the same property is declared more than once before the reference, @(prop) returns the last value. This mirrors CSS cascade behavior within a single block.

.box {
  color: red;
  color: blue;
  outline-color: @(color);
}
.box {
  color: red;
  color: blue;
  outline-color: blue;
}

valid: inside a {{ }} expression

@(prop) works inside {{ }} too. The compile-time lookup returns a quoted string that's embedded in the JS expression, enabling JS operations on CSS values.

.box {
  padding: 16px;
  margin: {{ parseInt(@(padding)) * 2 }}px;
}
.box {
  padding: 16px;
  margin: 32px;
}

valid: custom property lookup

@(--custom) works for CSS custom properties. The -- prefix is included in the lookup pattern.

.box {
  --accent-color: blue;
  color: @(--accent-color);
}
.box {
  --accent-color: blue;
  color: blue;
}

valid: sibling trees are isolated - preserved

Properties from sibling selector trees are invisible. Each top-level rule starts a fresh tree - .sidebar's properties can't leak into .main. Unresolved @(prop) passes through unchanged, enabling PostCSS plugins and future CSS features to process it.

.sidebar {
  border: dotted;
}

.main {
  outline: @(border);
}
.sidebar {
  border: dotted;
}

.main {
  outline: @(border);
}

valid: doesn't cross @media boundaries - preserved

In v0, @(prop) stops at at-rule boundaries. The @media block is a separate scope - it can't see properties declared outside. Unresolved @(prop) passes through unchanged.

This restriction may be relaxed in future versions for specific at-rule types where cross-boundary resolution is unambiguous.

.box {
  padding: 16px;
}

@media (min-width: 768px) {
  .box {
    margin: @(padding);
  }
}
.box {
  padding: 16px;
}

@media (min-width: 768px) {
  .box {
    margin: @(padding);
  }
}

valid: undeclared property - preserved

Referencing a property that hasn't been declared anywhere in the selector tree passes through unchanged. This enables PostCSS plugins or future CSS features to process it downstream.

.box {
  color: @(font-size);
}
.box {
  color: @(font-size);
}

valid: forward reference - preserved

@(prop) only looks backward. If the property is declared after the reference, it doesn't exist yet from the accumulator's point of view. The unresolved @(prop) passes through unchanged.

.box {
  outline: @(border);
  border: solid;
}
.box {
  outline: @(border);
  border: solid;
}

valid: self-reference protection

@(prop) cannot reference its own declaration - only the string before the current position is searched. This prevents infinite recursion naturally. The unresolved @(prop) passes through unchanged.

.box {
  background: @(background);
}
.box {
  background: @(background);
}

valid: build-time @() vs browser-runtime var()

@(--radius) resolves at build time — the value is baked into CSS. var(--radius) stays as-is for browser runtime resolution. Both are useful for different purposes.

.card {
  --radius: 8px;
  --color: blue;
  border-radius: @(--radius);
  color: var(--color);
}
.card {
  --radius: 8px;
  --color: blue;
  border-radius: 8px;
  color: var(--color);
}

valid: custom property parent walk-up

Same as standard properties — if the custom property isn't in the current block, resolution walks up to the parent.

.card {
  --spacing: 16px;
  .header {
    padding: @(--spacing);
  }
}
.card {
  --spacing: 16px;
  .header {
    padding: 16px;
  }
}