w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.43k stars 652 forks source link

[css-values-5] Boolean values in CSS custom properties #10593

Open brandonmcconnell opened 1 month ago

brandonmcconnell commented 1 month ago

Abstract

This proposal aims to introduce native support for boolean values in CSS custom properties by allowing a new <boolean> type in the @property rule. This enhancement aligns with existing boolean handling mechanisms in the CSS engine and extends them to user-defined custom properties. It enables developers to leverage true boolean logic within CSS for more readable and maintainable styles.

With the acceptance of if(), CSS is already dealing in boolean values, more directly than it ever has before. Introducing a way to reuse those conditions would make dealing with conditional/boolean expressions even more useful, especially in the case of more complex or lengthy boolean expressions.

If I understand correctly, any expression can already be stored in a CSS variable and is evaluated later, so some of this is technically already possible, but not type-safe the ways that other custom properties declared using @property are.

Motivation

Currently, CSS custom properties support a range of data types including numbers, lengths, colors, and strings, but they lack native support for boolean values. One common technique to work around this is to emulate boolean values using integers (0 and 1) or toggles using empty values (The -​-var: ; hack...), which can be both error-prone and less intuitive.

Introducing a reusable <boolean> type will streamline conditional styling and improve readability and type safety.

Syntax

Declaring a Boolean Custom Property

@property --selected {
  syntax: "<boolean>";
  initial-value: false;
  inherits: true;
}

Using @property is optional here, but it does yield some benefits in terms of type safety.

Using Boolean Custom Properties

label {
  --selected: false;
  &:has(:checked) { --selected: true; }
  grid-template-columns: auto if(var(--selected) ? 20px : 0);
  svg { opacity: if(var(--selected) ? 1 : 0); }
}

In this example, the custom property --selected is declared as a boolean type, simplifying its use in conditional logic.

Without this, a developer might resort to using mathematical expressions that—while equally dry and even shorter-form in this case—are not as intuitive and may take time to recognize that the math is essentially acting as a boolean switch.

Here is how that same example might look using a "math-based switch":

label {
  --selected: 0;
  &:has(:checked) { --selected: 1; }
  grid-template-columns: auto calc(var(--selected) * 20px);
  svg { opacity: var(--selected); }
}

This is a simple example, but this mathematical approach can easily become more complex depending on the use case.

Benefits

Type Safety

By introducing a native boolean type, the CSS engine can ensure that only valid boolean values are assigned to boolean custom properties, thus reducing errors.

Reusability and Maintainability

Boolean custom properties can be reused across multiple styles, reducing redundancy and making stylesheets more maintainable.

JavaScript Interoperability

Boolean custom properties can be easily manipulated through JavaScript, allowing for dynamic and interactive style changes. The type safety can be enforced using the CSS @property rule.

Using JavaScript, developers can interact with and manipulate boolean custom properties while benefiting from type safety.

Example with JavaScript

<label id="checkbox-label">
  <input type="checkbox" id="checkbox">
  Check me
  <svg>Icon</svg>
</label>

<script>
  const checkbox = document.getElementById('checkbox');
  const label = document.getElementById('checkbox-label');

  checkbox.addEventListener('change', (event) => {
    label.style.setProperty('--selected', checkbox.checked);
  });

  // Observing boolean custom property changes
  const observer = new MutationObserver(() => {
    const selected = label.style.getPropertyValue('--selected') === 'true';
    console.log('Selected:', selected);
  });

  observer.observe(label, { attributes: true, attributeFilter: ['style'] });
</script>

In this example, the --selected custom property is directly manipulated using JavaScript, and changes are observed to reflect the dynamic state of the checkbox.

Crissov commented 1 month ago

While CSS has properties with just two valid values available, it does not have boolean values, i.e. keywords like true and false. This verbosity makes it easier to extend the property later on. Queries have boolean results, though. Pseudo functions need to return valid values, so cannot be boolean in the sense requested here.

I understand that some authors use Custom Properties as if there were boolean values, probably because they are used to them from Javascript or other programming languages. I do not think this should be anything more than condoned by the specification.

The furthest it could go, in my opinion, would be to add two descriptors to @property that would each take one value value that will be considered truish and falsish, respectively.

romainmenke commented 1 month ago

See : https://drafts.csswg.org/mediaqueries-5/#custom-mq

@custom-media = @custom-media <extension-name> [ <media-query-list> | true | false ];

If a <media-query-list> is given, the custom media query evaluates to true if the <media-query-list> it represents evaluates to true, and false otherwise. If true or false is given, the custom media query evaluates to true or false, respectively.

I think this request largely mirrors what is already specified for custom media queries. (Although no implementer has taken up this part of the spec.)