Representative interview topic

Frontend interview: How do CSS @functions reuse design tokens?

FrontendHard
Offer.cc Editorial TeamPublished Updated

Question

Implement a CSS @function that turns spacing tokens into responsive lengths, then explain its boundaries with custom properties, Sass functions, and browser fallback.

1. Prompt and scope

A design system has spacing tokens, and components calculate padding from density and container width. The team wants fewer repeated calc() expressions while preserving plain CSS, theme overrides, and older-browser behavior. Design the solution with a CSS @function and explain a safe fallback.

2. What the interviewer is testing

  • Understand that @function defines a CSS custom function whose name is a dashed-ident beginning with --.
  • Declare parameters, defaults, optional types, and result, while knowing the body does not return like JavaScript at the first declaration.
  • Distinguish custom functions, custom properties, Sass build-time functions, and runtime cascading.
  • Handle feature detection, theme inheritance, cycles, compatibility, and accessible progressive enhancement.

3. Questions to clarify first

  1. Which target browsers lack CSS Functions, and is a polyfill allowed?
  2. Are tokens fixed lengths, percentages, or values that may include calc() and container units?
  3. Should invalid arguments use defaults, invalidate the declaration, or use a component fallback?
  4. Do themes define tokens at :root, component scope, or a Shadow DOM boundary?

4. Thirty-second answer

I would put a working static padding before the enhanced declaration, then override it inside @supports. The function uses typed and defaulted parameters and returns a length through result. Custom properties supply theme inputs; the function performs calculation. Unsupported browsers ignore the unknown function and keep the earlier fallback.

5. Step-by-step deep dive

Step 1: Define parameters and the result

css
@function --space(--step <integer>: 2, --base <length>: 4px) {
  result: calc(var(--base) * var(--step));
}

Parameters are local custom properties and can declare CSS types and defaults. result supplies the final value; it is not an early JavaScript return, so the function follows CSS declaration processing rules.

Step 2: Connect the function to tokens

css
:root {
  --space-base: 4px;
}

.card {
  padding: 16px;
  padding: --space(3, var(--space-base));
}

The first declaration is the older-browser fallback; the second applies when the custom function parses. Tokens still cascade through custom properties, so a theme or component scope can override --space-base.

Step 3: Handle types and invalid values

Type constraints reject values that do not match the grammar. Defaults cover omitted arguments, not arbitrary strings that should become lengths. Test negative values, wrong units, and missing tokens, and never concatenate user-controlled strings into CSS function arguments.

Step 4: Understand scope and dependencies

Function parameters behave as local custom properties inside the body, while the call site still follows cascade, inheritance, and layer order. A token used by the function must not depend back on the function result; a cycle makes the computed declaration invalid.

Step 5: Feature detection and progressive enhancement

css
.card {
  padding: 16px;
}

@supports (padding: --space(3, 4px)) {
  .card {
    padding: --space(3, var(--space-base, 4px));
  }
}

@supports tests parsing ability, not every business branch. Keep a stable fallback, inspect computed layout, and ensure unsupported browsers still preserve readable text, focus indicators, and touch-target size.

6. Model high-quality answer

I would provide a static padding, then calculate the enhanced value with @function inside @supports. Custom properties carry theme values; typed parameters and result perform the calculation. I would test omitted arguments, wrong units, negative values, and cycles. Because support still varies, the fallback must stand alone, and browser checks should verify computed styles and accessible dimensions.

7. Common mistakes

  • Treating CSS @function as a Sass build-time function → production browsers ignore it → keep a runtime fallback and feature-detect.
  • Passing a unitless number to a <length> parameter → declaration becomes invalid → specify types and defaults clearly.
  • Assuming the function mutates a custom property → theme overrides fail → separate token inputs from the result output.
  • Writing only @supports with no base declaration → unsupported browsers lose spacing → write the fallback first.
  • Making functions reference each other → cyclic dependencies invalidate values → keep token dependencies one-way.

8. Follow-up questions

Follow-up 1: How is it different from a custom property?

A custom property stores a cascading value; a custom function accepts parameters and computes a result. The function can reuse calculation logic while reading inherited token inputs.

Follow-up 2: How is it different from a Sass function?

A Sass function emits CSS at build time and cannot observe runtime themes or container state. A CSS custom function runs during browser cascading and computation, so it is dynamic but needs compatibility handling.

Follow-up 3: How do you detect support?

Use @supports (padding: --space(1, 4px)) to detect parsing, while retaining the preceding fallback. Tests should also inspect the computed value in a real browser.

Follow-up 4: How do you avoid cycles?

Keep base tokens dependent on raw values, let functions read tokens and emit derived values, and prohibit tokens from referencing properties that include the function result. Check invalid computed values in DevTools.

Public sources

Related questions