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.