Skip to content
HELiX

Drupal Integration

apps/docs/src/content/docs/framework-integration/drupal Click to copy
Copied! apps/docs/src/content/docs/framework-integration/drupal

HELIX is purpose-built for Drupal. The full integration guide lives in the Drupal Integration section. This page is a quick-start summary covering the essential patterns.

Two options are available:

Terminal window
npm install @helixui/library

Reference the built files from your theme’s .libraries.yml.

mytheme.libraries.yml
helix:
js:
https://cdn.jsdelivr.net/npm/@helixui/library/dist/index.js: { type: external, attributes: { type: module } }

See CDN Installation for the full setup.

HELIX elements are standard HTML — use them directly in .html.twig templates:

{# templates/block--my-block.html.twig #}
<hx-button variant="primary" type="button">
{{ 'Save changes'|t }}
</hx-button>

Use Twig variable interpolation to set attributes:

<hx-button
variant="{{ button_variant|default('primary') }}"
{% if disabled %}disabled{% endif %}
>
{{ label }}
</hx-button>

Several HELIX components use hx-size instead of the native size attribute for setting dimensions (e.g., sm, md, lg). The prefix avoids collisions with the native HTML size attribute (which has numeric semantics on <input> and <select>). It does share the hx-* namespace with htmx but does not collide with any current htmx attribute name.

Components that use hx-size: hx-button, hx-icon-button, hx-spinner, hx-avatar, hx-badge, and others.

{# Correct — use hx-size, not size #}
<hx-button variant="primary" hx-size="lg">Submit</hx-button>
{# Wrong — size is NOT the same as hx-size #}
<hx-button variant="primary" size="lg">Submit</hx-button>

Follow HELIX’s boolean attribute semantics. Output the attribute name without a value when true; omit it entirely when false:

{# Correct #}
<hx-button {% if is_disabled %}disabled{% endif %}>
{{ label }}
</hx-button>
{# Wrong — disabled="false" still disables #}
<hx-button disabled="{{ is_disabled ? 'true' : 'false' }}">
{{ label }}
</hx-button>

Twig child content maps to the default slot:

<hx-card>
<span slot="heading">{{ card_title }}</span>
<div>{{ card_body }}</div>
</hx-card>

See Slots in Twig for named slot patterns.

Drupal Behaviors are the correct place to attach JavaScript event listeners to HELIX components. They re-fire on AJAX updates.

js/my-feature.js
(function (Drupal) {
Drupal.behaviors.myFeature = {
attach(context) {
const buttons = context.querySelectorAll('hx-button[data-my-action]');
buttons.forEach((btn) => {
if (btn.dataset.behaviorAttached) return; // prevent double-attach
btn.dataset.behaviorAttached = 'true';
btn.addEventListener('hx-click', (event) => {
Drupal.ajax({ url: btn.dataset.url }).execute();
});
});
},
};
})(Drupal);

See Drupal Behaviors — With Web Components for detailed patterns.

HELIX form components participate in native HTML forms. In Drupal, combine with the Form API’s #attributes key:

$form['email'] = [
'#type' => 'html_tag',
'#tag' => 'hx-text-input',
'#attributes' => [
'name' => 'email',
'type' => 'email',
'required' => TRUE,
'placeholder' => $this->t('Enter your email'),
],
];

For full Drupal Form API element plugins, see Form API Integration.

For performance, load only the components used on each page:

mytheme.libraries.yml
helix-button:
js:
/libraries/helixui/dist/components/hx-button/index.js: { attributes: { type: module }, minified: true }
helix-text-input:
js:
/libraries/helixui/dist/components/hx-text-input/index.js: { attributes: { type: module }, minified: true }

Adjust the /libraries/helixui/... prefix to match where you mount @helixui/library in your Drupal site. The canonical generated library file ships in the @helixui/drupal-starter package (helixui.libraries.yml, auto-generated by pnpm run generate:drupal-libraries) — use it as the source of truth for path conventions.

See Per-Component Loading for the full strategy.

This page covers the basics. The complete Drupal integration guide includes: