html-computer

Problem

Many component libraries, especially those built with vanilla Web Components, follow a specific way of dealing with boolean attributes, specified by the HTML Living Standard. Many Frameworks however do not apply the same standards and patterns in their approach, including but not limited to, frameworks that rely on JSX. We try to reconcile these two, by showing how we can use standards compliant syntax in some JavaScript frameworks.

What does the Standard say

The HTML Living Standard states that

The presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value. If the attribute is present, its value must either be the empty string or a value that is an ASCII case-insensitive match for the attribute’s canonical name, with no leading or trailing whitespace.

In practical terms, this means that to represent a truthy attribute on an HTML element, we need to provide the attribute and assign either it’s own name as a string, or an empty string. The latter works because attribute values are always strings, and strings, even non-empty ones, are always truthy.

For example, the following are truthy:

<my-component is-open="is-open"></my-component>
<my-component is-open=""></my-component>
<my-component is-open="false"></my-component>

The last one is counter-intuitive, but this works out to be truthy because (again) all attribute values are strings, even those named “false”.

Note that although the standard prohibits the setting of “false” as a value in some cases, these prohibitions are non-normative, and in practice it is possible to do this even on standard elements.

For example, <input disabled="false" /> actually means that the input is disabled, because disabled="false" evaluates to a truthy (tested in Firefox at least). Weird, but that’s why the recommendation is to stick to empty strings or a value that matches the attribute.

On the other hand, to represent a falsy attribute, we omit the attribute altogether, like so:

<my-component></my-component>

Our Frameworks

The above syntax is not the chosen form of most frameworks, each of which have their own way of dealing with booleans. Let’s tackle each one in turn, and show how we can alter each framework’s respective approach to achieve HTML Standards compliance.

Set Boolean attribute in React

The React way of passing in booleans into child components is like so:

const [isOpen, setIsOpen] = useState(true)

<ChildComponent isOpen={isOpen}/>

Where isOpen is simply a state variable, and passed directly into the component with curly braces.

Wrong way

However, when using a component that needs to receive the attribute in a HTML Standards compliant way, a direct port of the above will not work:

<my-component is-open={isOpen}></my-component>

It fails because is-open={isOpen} evaluates to is-open="true" or is-open="false" depending on whether isOpen is true or false. As covered earlier, the latter does not lead to an omission of the attribute, and so is considered truthy according to the specification.

Correct way

To set HTML compliant boolean attributes in React, we need to use the following syntax:

<my-component is-open={isOpen ? 'is-open' : null}></my-component>  

When isOpen is true, the expression evaluates to is-open="is-open" which is standards compliant.

When isOpen is false, the expression evaluates to null, and it’s corresponding attribute is-open will be removed from the element.

Set Boolean attribute in Angular

The Angular way of passing in booleans into child components is like so:

<child-component [isOpen]="isOpen"/>

Where [isOpen] represents a property binding, while "isOpen" is a property set in the parent class like so:

@Component({...})
export class ParentComponent {
  isOpen = true

  toggleIsOpen() {
    isOpen = !isOpen
  }
}

Wrong ways

However, when using a component that needs to receive the attribute in a HTML Standards compliant way, the following will not work:

<my-component [is-open]="isOpen"></my-component>

The above fails, because as mentioned, [is-open] represents a property binding, which binds the isOpen property to the my-component element as a JavaScript object property. The HTML standard expects booleans to be set as attributes instead. If you render the above in a browser, you will notice that is-open will never be set in the my-component element, regardless of its value – that’s because only attributes are reflected in the browser DOM inspector.

We can try to rectify this problem by using attribute binding syntax, which will bind isOpen to an attribute is-open instead:

<my-component [attr.is-open]="isOpen"></my-component>

Unfortunately, this will also fail, but for a different reason (yay, progress?): [attr.is-open]="isOpen" evaluates to is-open="true" or is-open="false". These are both strings so no truthy-falsy toggling happens, and neither represents a falsy by removal of the is-open attribute.

Correct way

To set HTML compliant boolean attributes in Angular, we need to use the following syntax:

<my-component [attr.is-open]="isOpen ? 'is-open' : null"></my-component>

This creates an attribute is-open="is-open" when isOpen is true, but removes it when isOpen is false by setting the attribute to null.

Set Boolean attribute in Vue 3

The Vue 3 way of passing in booleans into child components is like so:

<child-component :isOpen="isOpen"></child-component>

Where :isOpen is a shorthand for a v-bind directive, which is an attribute binding. "isOpen" is a ref, defined in a component’s template, and declared and returned from that component’s setup() function, like so:

import { ref } from 'vue'

export default {
  setup() {
    const isOpen = ref(true)

    return {
      isOpen
    }
  }
}

Wrong way

However, when using a component that needs to receive the attribute in an HTML Standards compliant way, the following will not work:

<my-component :is-open="isOpen"></my-component>

Vue child components are usually declared with prop types, to specify the kind of types to expect from an attribute. Based on these, Vue decides under the hood how to handle the respective attributes. This prop type declaration enables Vue to know to omit the attribute in the event of a falsy value.

Components that follow the HTML spec however do not share this declarative type syntax, and so Vue will simply interpret all attribute values as strings. Therefore, the example above doesn’t toggle at all, because when isOpen is true or false, it evaluates to is-open="true" and is-open="false" respectively, which (both being strings) will set the attribute, and hence neither results in an omitted attribute.

Correct way

To set HTML compliant boolean attributes in Vue 3, we need to use the following syntax:

<my-component :is-open="isOpen ? 'is-open' : null"></my-component>

Setting an attribute to null in Vue will omit it from the element.

Conclusion and Inference: Use null

Based on our examples above, we can generalise that setting a falsy attribute to null will generally be the correct approach when trying to implement HTML Standards compliant booleans in our 3 frameworks. In all of these, some way to toggle the attribute between a value of type string (even an empty string) and null results in the attribute being added to and removed from the HTML element respectively.