Skip to content

Add --default(…) option in --value(…) to support fallback values#19989

Open
RobinMalfait wants to merge 1 commit intomainfrom
feat/support-default-css-functional-utility
Open

Add --default(…) option in --value(…) to support fallback values#19989
RobinMalfait wants to merge 1 commit intomainfrom
feat/support-default-css-functional-utility

Conversation

@RobinMalfait
Copy link
Copy Markdown
Member

@RobinMalfait RobinMalfait commented Apr 28, 2026

This PR adds a new --default(…) option that can be used inside a --value(…) such that functional utilities without an explicit value can still be defined as a function utility. It would also allow you to use a functional utility without a value and with a modifier, e.g.: shadow/50.


This allows us to re-implement functional utilities with a default value in CSS using @utility.

Used the explicit --default(…) argument of --value(…) for a few reasons.

  1. It's explicit about being a falllback value. If you have @utility foo-*, then you want to be able to use foo, but foo-bad should not compile.
  2. When --value(…) is used in (complex) property values (think a bunch of calc(…) expressions), then we don't need a separate property for this.

One of the ideas was to have a literal fallback:

@utility tab-* {
  tab-size: 4;
  tab-size: --value(number);
}

For tab, this would compile to:

.tab {
  tab-size: 4;
}

For tab-123, this would compile to:

.tab {
  tab-size: 4;
  tab-size: 123;
}

Getting rid of the tab-size: 4 would be an option, but it's a common pattern in real CSS for fallback values (think hex background color, over a more modern oklch color).

For tab-foo, this would compile to:

.tab {
  tab-size: 4;
}

Which means that we have an infinite amount classes that would result in the same class, which is bad. We could special case this one because the internal value would still be null, but it might be too confusing.

This syntax without the --default(…) also means repetition of certain properties. Add --modifier(…) to the mix, and there is even more repetition going on.

Another option to consider is that the default fallback is just another option in the --value(…, 4), but if a default fallback is a keyword, then there is a chance that this might conflict with actual keywords we interpret.

Main motivation is to be able to re-implement utilities such as shadow/50 purely in CSS. It's also something we support in the JS based APIs, but not in the CSS based one, so while it's a "new" feature, it's more like a missing feature right now, and often a reason for people to use the JS based APIs instead.

Fixes: #16824

Test plan

  1. Added a handful of new tests to make sure this functionality works
  2. Existing tests still pass

@RobinMalfait RobinMalfait marked this pull request as ready for review April 29, 2026 09:43
@RobinMalfait RobinMalfait requested a review from a team as a code owner April 29, 2026 09:43
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Walkthrough

Adds tests enforcing that functional utilities require --value(...) and compile to an empty string when --value(...) is omitted or unresolved. createCssUtility no longer auto-appends a trailing -* when a --value(...) argument contains nested functional syntax. Resolution now branches: existing resolveValueFunction handles present candidate.value; a new resolveDefaultValueFunction extracts --default(...) nodes when candidate.value is missing. Candidate validity checks were tightened to require --value(...) usage and at least one resolvable --value(...) branch. No public API changes.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a --default(…) option inside --value(…) to support fallback values in functional utilities.
Linked Issues check ✅ Passed The PR implements support for utility modifiers on utilities without explicit arguments by enabling --default(…) in --value(…), directly addressing the requirement in issue #16824.
Out of Scope Changes check ✅ Passed All changes (test additions and utility implementation modifications) are directly scoped to the feature objective of adding --default(…) support within --value(…) for functional utilities.
Description check ✅ Passed The pull request description clearly relates to the changeset, explaining the motivation, implementation details, and use cases for adding --default(…) support to functional CSS utilities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

This allows us to re-implement functional utilities with a default value
in CSS using `@utility`.

Used the explicit `--default()` argument of `--value()` for a few reasons.

1. It's explicit about being a falllback value. If you have `@utility
   foo-*`, then you want to be able to use `foo`, but `foo-bad` should
   not compile.
2. When `--value(…)` is used in (complex) property values (think a bunch
   of `calc(…)` expressions), then we don't need a separate property for
   this.

One of the ideas was to have a literal fallback:
```css
@Utility tab-* {
  tab-size: 4;
  tab-size: --value(number);
}
```

For `tab`, this would compile to:
```css
.tab {
  tab-size: 4;
}
```

For `tab-123`, this would compile to:
```css
.tab {
  tab-size: 4;
  tab-size: 123;
}
```
Getting rid of the `tab-size: 4` would be an option, but it's a common
pattern in real CSS for fallback values (think hex background color,
over a more modern `oklch` color).

For `tab-foo`, this would compile to:
```css
.tab {
  tab-size: 4;
}
```
Which means that we have an infinite amount classes that would result in
the same class, which is bad. We could special case this one because the
internal `value` would still be `null`, but it might be too confusing.

This syntax without the `--default` also means repetition of certain
properties.

Add `--modifier(…)` to the mix, and there is even more repetition going
on.

Another option to consider is that the default fallback is just another
option in the `--value(…, 4)`, but if a default fallback is a keyword,
then there is a chance that this might conflict with actual keywords we
interpret.
@RobinMalfait RobinMalfait force-pushed the feat/support-default-css-functional-utility branch from 435aba9 to 8c4744c Compare April 29, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v4] Utility modifiers only work with arguments

1 participant