In CSS, how do you declare a custom property and use it in a value with var(), and what does the second argument in var(--name, fallback) do?
answer
- two dashes make it a property
- cascades and inherits like color
- comma introduces the fallback
- everything after first comma counts
- covers missing, not wrong
basics
~20 sDeclare a custom property as a normal declaration whose name starts with two dashes, such as --gap: 12px, and read it back with var(--gap). The optional second argument, var(--gap, 8px), applies only when --gap is not defined.
solid answer
~50 sA custom property is any declaration whose name begins with two dashes: `.card { --gap: 12px; }`. It is a real CSS property, so it takes part in the cascade and it inherits by default, which means every descendant of `.card` sees `--gap`. You consume it with `var(--gap)` anywhere a value is allowed, including inside `calc()` and inside shorthands: `padding: var(--gap)`. Names are case-sensitive, so `--Gap` and `--gap` are two different properties. `var()` accepts an optional fallback after the first comma — `var(--gap, 8px)` resolves to `8px` when `--gap` has not been set on that element — and everything after that first comma counts as the fallback, so `var(--font, Georgia, serif)` is legal. The fallback covers the *missing* case only, not the case where the property exists but holds something the target property cannot use.
code
css · 18 lines:root {
--gap: 12px;
--accent: #1a73e8;
}
.card {
padding: var(--gap);
border: 1px solid var(--accent);
}
.card--tight {
--gap: 4px;
}
.sidebar {
/* --gap is not set here, so the fallback applies */
margin: var(--sidebar-gap, 16px);
}go deeper
Be ready to write the syntax from memory: --gap: 12px to declare, var(--gap) to use, var(--gap, 8px) for a fallback when the property is missing. Say plainly that the name needs both dashes.
Explain that a custom property is an ordinary inherited property that cascades, so where you declare it decides which elements see it, and that everything after the first comma in var() is the fallback.
Show the failure modes you have debugged: a fallback that never fires because the property is defined but wrong, a bare number that will not concatenate with a unit, and a case-mismatched name that silently resolves to nothing.
Own the convention: where defaults live, whether values carry units, and how deep var() chains are allowed to nest before a stylesheet becomes untraceable for the next reader.
## What a custom property actually is A custom property is a CSS declaration whose property name starts with two hyphens, for example `--gap: 12px`. The standard properties are a fixed list defined by the specification; custom property names are yours to invent. The browser does not know or care what `--gap` means — it stores the declared value as a stream of tokens and hands that stream back wherever you ask for it. The important consequence is that this is a *property*, not a special build-time construct. It lives inside a rule with a selector, it competes in the cascade like any other declaration (later wins over earlier at equal specificity, higher specificity wins, `!important` wins), and it **inherits by default**, flowing down to descendants the way `color` does. ## Declaring one ```css :root { --gap: 12px; --brand: #1a73e8; } .card { --gap: 8px; /* applies to .card and everything inside it */ } ``` `:root` matches the document's root element, which is where people usually park global defaults. Nothing forces that: you can declare a custom property on any selector, and even on a single element through its `style` attribute. Two details bite beginners. First, names are **case-sensitive** — `--Brand` and `--brand` are unrelated properties, unlike `COLOR` and `color`. Second, the declared value is almost unrestricted: `--x: 1px solid;` and even `--x: hello world;` parse fine, because validity is not checked until something tries to use the value. ## Reading it back with var() ```css .card { padding: var(--gap); border: 1px solid var(--brand); width: calc(100% - var(--gap) * 2); } ``` `var()` can appear anywhere a value, or part of a value, is allowed. It can sit inside `calc()`, inside a shorthand, and inside the value of another custom property, which is how layered token sets are built: ```css :root { --space: 4px; --gap-lg: calc(var(--space) * 4); } ``` Three things `var()` cannot do, and interviewers like all three: - It cannot build a **property name**. `var(--prop): red` is not valid CSS. - It cannot appear in a **selector**. - It cannot appear in a **media query condition**; `@media (min-width: var(--bp))` does not work in browsers today. ## The fallback argument `var()` takes an optional second argument after the first comma: ```css .card { padding: var(--gap, 8px); } ``` If `--gap` is not defined on the element being styled, the substitution uses `8px`. "Not defined" is precise: the property has never been set, or was explicitly reset, so it holds the guaranteed-invalid value. Everything after the *first* comma is the fallback, commas included, which is why font stacks work: ```css font-family: var(--font, Georgia, "Times New Roman", serif); ``` Fallbacks nest, so you can chain preferences: `var(--a, var(--b, 4px))` tries `--a`, then `--b`, then `4px`. ## What the fallback does not do The fallback only covers the missing case. If `--gap` **is** defined but holds something the target property cannot accept — say `--gap: red` used in `padding: var(--gap, 8px)` — you do not get `8px`. The substitution happens, the result is nonsense for `padding`, and the declaration becomes invalid at computed-value time, which is a different and more surprising failure mode than a missing property. ## Tokens are spliced, not glued A classic trap: ```css :root { --size: 10; } .box { width: var(--size)px; } /* does not work */ .box { width: calc(var(--size) * 1px); } /* works */ ``` Substitution inserts the stored tokens next to the surrounding tokens; it does not concatenate text into a new dimension token, so `10` and `px` never become `10px`. Multiply by a unit inside `calc()` instead. This is also why storing unit-bearing values (`--size: 10px`) is usually simpler than storing bare numbers. ## The shape you will actually write Global defaults on `:root`, local overrides on the component or a variant class, and a single consuming rule that never has to change: ```css :root { --accent: #1a73e8; } .button { background: var(--accent); } .button--danger { --accent: #c5221f; } ``` That one-declaration-many-values shape is the whole point of custom properties, and it is only possible because they cascade and inherit like real properties.
- If --size is 10, why does width: var(--size)px not produce a 10px width?Because substitution splices token streams rather than concatenating text: the `10` and the `px` never fuse into a single dimension token, so the value is not a valid length and the declaration is dropped at computed-value time. Multiply by a unit instead: `width: calc(var(--size) * 1px)`. Storing the unit in the property, `--size: 10px`, avoids the problem entirely.
- Can you use var() to choose which property you are setting, like var(--prop): red?No. `var()` substitutes into values only, never into property names, selectors, or at-rule preludes. A custom property's value is a token stream the browser splices into a value position; property names are parsed before any substitution happens. If you need to switch which property changes, switch the rule or the class, not the property name.
- Are custom property names case-sensitive?Yes, unlike standard property names. `--Brand` and `--brand` are two independent properties, and `var(--Brand)` will not find a value declared as `--brand`. This is a common source of silent failures because the result is not an error — the property is simply missing, so you get the `var()` fallback if you supplied one, and invalid-at-computed-value-time behaviour if you did not.
saying these in an interview costs you the question
- Thinks the var() fallback also rescues invalid defined values
- Writes var(gap) or var(-gap) instead of var(--gap)
- Assumes --Gap and --gap are the same property
- Expects var(--n)px to concatenate into a length
- Believes var() works inside a media query condition