Skip to content
HELiX

Boolean Attributes

apps/docs/src/content/docs/guides/boolean-attributes Click to copy
Copied! apps/docs/src/content/docs/guides/boolean-attributes

HTML boolean attributes follow a simple rule: the presence of the attribute means true; the absence means false. The value of the attribute is irrelevant.

<!-- All three of these set `disabled` to true -->
<hx-button disabled></hx-button>
<hx-button disabled=""></hx-button>
<hx-button disabled="false"></hx-button> <!-- ⚠️ STILL DISABLED -->
<!-- Only this sets `disabled` to false -->
<hx-button></hx-button>

This is how the HTML specification defines boolean attributes (see HTML Living Standard §2.3.2), and HELIX components follow it exactly.


The confusion arises when a boolean property defaults to true. A consumer may try to opt out by writing:

<!-- Intent: hide the icon. Actual result: icon still shows -->
<hx-alert show-icon="false">Your message</hx-alert>

Because show-icon is a boolean attribute, its presence flips the property to true — regardless of the "false" string value. The attribute is present, so showIcon is true.


Components with Boolean Properties Defaulting to true

Section titled “Components with Boolean Properties Defaulting to true”

The following HELIX boolean properties default to true (per packages/hx-library/custom-elements.json). These are the cases where you cannot set false from plain HTML alone:

ComponentPropertyAttributeDefaultEffect when absent
hx-banneropenopentrueBanner is hidden when explicitly removed via JS / Lit binding
hx-dialogcloseOnBackdropclose-on-backdroptrueDialog will not close on backdrop click
hx-patient-bannerenforceIdentifierRuleenforce-identifier-ruletruePatient-identifier validation is skipped
hx-skeletonanimatedanimatedtruePulse animation disabled
hx-tablefullWidthfull-widthtrueTable renders at intrinsic width instead of stretching

For comparison, properties that look like they should be default-true but are not (they default to false, so adding the attribute enables the feature):

ComponentPropertyDefault
hx-alertopen, showIcon, dismissible, accentfalse — alert is hidden / icon hidden / dismissible off / no accent by default
hx-code-snippetcopyable, inline, wrap, lineNumbersfalse — copy button hidden unless opted in
hx-dialogmodalfalse — non-modal by default; use modal attribute or showModal() for modal behavior

Pattern 1: Omit the attribute (pure HTML, for default-false props)

Section titled “Pattern 1: Omit the attribute (pure HTML, for default-false props)”

For properties that default to false, omitting the attribute leaves them off — adding it (even as attr="false") enables the feature.

<!-- show-icon defaults to false — icon is hidden -->
<hx-alert variant="info" open>Alert without icon.</hx-alert>
<!-- show-icon attribute present → showIcon = true (icon visible) -->
<hx-alert variant="info" open show-icon>Alert with icon.</hx-alert>
<!-- WRONG: attribute string "false" still flips the property to true -->
<hx-alert variant="info" open show-icon="false">Still shows the icon</hx-alert>

For properties that default to true (the table above), the inverse rule applies — markup alone cannot turn the property off. There is no HTML syntax that conveys false for a boolean attribute (see HTML Living Standard §2.3.2 — Boolean attributes). Use JavaScript or a template framework binding (Patterns 2–3 below).

Set the property directly on the element reference after it is connected:

// Disabling default-true properties from JS:
const dialog = document.querySelector('hx-dialog');
dialog.closeOnBackdrop = false; // ✅ Dialog ignores backdrop clicks
const skeleton = document.querySelector('hx-skeleton');
skeleton.animated = false; // ✅ Pulse animation disabled
const table = document.querySelector('hx-table');
table.fullWidth = false; // ✅ Table renders at intrinsic width
// Enabling default-false properties also works from JS, but in plain HTML
// you'd just add the attribute. JS is mostly useful for dynamic toggles:
const alert = document.querySelector('hx-alert');
alert.showIcon = true; // Equivalent to adding the show-icon attribute

In Lit templates, use the ? boolean binding prefix. This adds or removes the attribute based on the expression value:

html`
<!-- Adds show-icon attribute when showIcon is true (false → attribute absent) -->
<hx-alert ?show-icon=${this.showIcon} ?open=${this.alertOpen}>Message</hx-alert>
<!-- For a default-true prop, ?attr=${false} is the canonical way to disable
it from a template: -->
<hx-skeleton ?animated=${false}></hx-skeleton>
<hx-dialog ?close-on-backdrop=${false}>…</hx-dialog>
`

When the expression evaluates to false, Lit calls removeAttribute() — the attribute is absent, and the component property resolves to its own default or false.

In Twig, conditionally render boolean attributes using the conditional block syntax:

{# WRONG: always sets show-icon = true regardless of value #}
<hx-alert show-icon="{{ show_icon }}">{{ message }}</hx-alert>
{# CORRECT: only emit the attribute when true #}
<hx-alert
variant="{{ variant|default('info') }}"
{% if show_icon %}show-icon{% endif %}
>{{ message }}</hx-alert>

For default-true properties that need to be disabled in a Drupal context, use a Drupal behavior to set the property via JavaScript after the element is defined. The example below disables the dialog’s backdrop close behavior on dialogs marked with a data attribute from Twig:

my_module/js/my-module.behaviors.js
Drupal.behaviors.myModuleStrictDialog = {
attach(context) {
once('hx-dialog-strict', 'hx-dialog[data-strict]', context).forEach((el) => {
customElements.whenDefined('hx-dialog').then(() => {
el.closeOnBackdrop = false;
});
});
},
};
<hx-dialog
heading="{{ heading }}"
{% if strict_mode %}data-strict{% endif %}
>{{ message }}</hx-dialog>

Some features are “on by default” because turning them off is the exception. For example:

  • hx-skeleton.animated defaults to true because the pulse motion is the visible “this is loading” signal — disabling it is the prefers-reduced-motion / hidden-skeleton case.
  • hx-dialog.closeOnBackdrop defaults to true because a backdrop click escape hatch is the standard dialog UX — false is the destructive-confirmation special case.
  • hx-table.fullWidth defaults to true because content-width tables are the rare exception in dashboards.

The trade-off is that disabling a default-true feature requires JavaScript (or a framework binding) rather than a plain HTML attribute. This is a known HTML limitation — the spec has no mechanism for a boolean attribute to convey false (see HTML Living Standard §2.3.2). Defaulting features that are commonly disabled (icons, copy buttons, modal mode) to false lets consumers opt in cleanly from plain HTML.


GoalMethod
Enable a default-false booleanAdd the attribute: <hx-button disabled>
Disable a default-true boolean (JS)el.closeOnBackdrop = false
Disable a default-true boolean (Lit)?close-on-backdrop=${false}
Disable a default-true boolean (Twig)Drupal behavior + JS property
Do not useattr="false" — this enables the feature