Skip to content

Custom functions

A custom function is a piece of Java that SCSS can call like any built-in. Register it under its SCSS signature and jsass marshals the arguments and the return value across the engine boundary for you.

SassCallback pow = ctx -> {
  var args = ctx.getArgs();
  var base = (SassNumber) args.get(0);
  var exp = (SassNumber) args.get(1);
  return SassNumber.of(Math.pow(base.getValue(), exp.getValue()), base.getUnit());
};

var options = StringOptions.builder()
    .url(URI.create("virtual:root.scss"))
    .functions(Map.of("pow($base, $exp)", SassFunction.builder().callback(pow).build()))
    .build();
… and in your stylesheet
.demo {
  font-size: pow(2, 3) * 1px;
}

The map key is the full SCSS signature, argument names included — that is how dart-sass learns the arity and the parameter names. The SassFunctionContext handed to the callback carries that same signature plus the args in declaration order, as an unmodifiable list.

Values

All values crossing the boundary implement the sealed interface SassValue. There are seven implementations and each has static factories; the constructors are not public.

Type Create it with Read it with
SassNumber SassNumber.of(3.5, "px"), SassNumber.unitless(42) getValue(), getUnit()
SassString SassString.quoted("a"), SassString.unquoted("a") getValue(), isQuoted()
SassColor SassColor.rgb(59, 130, 246, 1.0) and one factory per space getSpace(), getChannel1()getAlpha()
SassBoolean SassBoolean.of(true), SassBoolean.TRUE, SassBoolean.FALSE isValue()
SassNull SassNull.NULL
SassList SassList.comma(items), .space(items), .slash(items) getContents(), getSeparator(), isBracketed()
SassMap SassMap.of(map) getContents()

Lists and maps defensive-copy their contents and are immutable afterwards; SassList.bracketed() returns a bracketed copy rather than mutating.

Colors

SassColor carries a color-space tag, three channels and an alpha. Use the factory for the space you are working in — the channel meaning follows from it:

Space Factory Channels
RGB, SRGB, SRGB_LINEAR, DISPLAY_P3, A98_RGB, PROPHOTO_RGB, REC2020 SassColor.rgb(r, g, b, a), .srgb(…), .srgbLinear(…), .displayP3(…), .a98Rgb(…), .prophotoRgb(…), .rec2020(…) red, green, blue
HSL SassColor.hsl(h, s, l, a) hue, saturation, lightness
HWB SassColor.hwb(h, w, b, a) hue, whiteness, blackness
LAB / OKLAB SassColor.lab(…) / SassColor.oklab(…) lightness, a, b
LCH / OKLCH SassColor.lch(…) / SassColor.oklch(…) lightness, chroma, hue
XYZ_D50 / XYZ_D65 SassColor.xyzD50(…) / .xyzD65(…), .xyz(…) x, y, z

rgb() validates its channels (0–255) and the alpha (0–1); the other spaces accept unbounded channels — gamut mapping is deliberately out of scope.

RGB does not survive a round trip

dart-sass normalises the legacy rgb space to srgb on read-back. A color built with SassColor.rgb(…) therefore comes back as ColorSpace.SRGB. Use SassColor.srgb(…) when you compare a returned color against an expected one.

A lookup function

Anything the callback can reach is fair game — a configuration object, a design-token map, a database:

private static final Map<String, SassColor> THEME = Map.of(
    "primary", SassColor.rgb(59, 130, 246, 1.0),
    "secondary", SassColor.hsl(217, 91, 60, 1.0));

SassCallback themeColor = ctx -> {
  var name = ((SassString) ctx.getArgs().get(0)).getValue();
  var color = THEME.get(name);
  if (null == color) {
    throw new IllegalArgumentException("Unknown theme color: " + name);
  }
  return color;
};
.demo { color: theme-color("primary"); }

When a function fails

Argument typing is the callback's job: cast to the SassValue subtype you expect. If the cast fails, or the callback throws anything else, jsass wraps it as a SassFunctionExecutionException and dart-sass reports it as a compilation error pointing at the call site. The original exception stays available through getCause().

In other words: throwing from a custom function is a supported way to reject bad input. It produces a normal Sass error, not a crashed compiler.