Module Installation
apps/docs/src/content/docs/drupal/installation/module Click to copy apps/docs/src/content/docs/drupal/installation/module The module approach packages HELiX as a reusable Drupal module that acts as a library provider. Once installed and enabled, all themes and modules across a Drupal installation (or multi-site network) can attach HELiX libraries by name — no per-theme npm setup, no per-theme CDN configuration.
This is the recommended approach for enterprise multi-site deployments, agency distribution to multiple clients, and any situation where you want a single authoritative source for HELiX version management.
When to Use Module-Based Installation
Section titled “When to Use Module-Based Installation”Well suited for:
- Multi-site installations (hospital systems, government portals, Acquia Site Factory tenants)
- Agencies distributing HELiX to multiple client projects via a private Composer repository
- Organizations that manage JavaScript dependencies through Composer rather than npm
- Architectures where the component library is decoupled from any specific theme
- Teams experienced with Drupal module development who find npm build pipelines unfamiliar
Not suited for:
- Single-site custom theme development (npm is simpler and more direct)
- Rapid prototyping (CDN is faster to set up)
- Teams unfamiliar with Drupal module architecture, install hooks, and configuration management
Architecture Overview
Section titled “Architecture Overview”A HELiX library module is a standard Drupal custom module that:
- Declares HELiX JavaScript files as Drupal library assets in a
.libraries.ymlfile - Stores the HELiX built files inside the module’s
libraries/directory - Optionally implements hooks for dynamic library configuration, environment switching, or version management
Any theme or module on the site attaches HELiX by referencing the module’s library machine name (e.g., hx_library/components) in .info.yml, Twig templates, or render arrays.
File structure:
web/modules/custom/hx_library/├── hx_library.info.yml # Module metadata and Drupal version compatibility├── hx_library.libraries.yml # Library definitions (JS/CSS asset paths)├── hx_library.module # Drupal hooks (optional)├── hx_library.install # Install, update, and requirements hooks├── config/│ ├── install/│ │ └── hx_library.settings.yml # Default configuration│ └── schema/│ └── hx_library.schema.yml # Config schema for type validation└── libraries/ └── helix/ └── dist/ ├── index.js # Full component bundle ├── css/ │ └── helix-all.css # CSS bundle └── components/ # Per-component files ├── hx-button/ │ └── index.js ├── hx-card/ │ └── index.js └── hx-text-input/ └── index.jsStep 1: Create Module Scaffold
Section titled “Step 1: Create Module Scaffold”Module Info File
Section titled “Module Info File”web/modules/custom/hx_library/hx_library.info.yml:
name: HELiX Librarytype: moduledescription: Provides HELiX web components as Drupal library assets for use across themes and modules.core_version_requirement: ^10 || ^11package: Custom
# Uncomment to hide from the Extend UI (infrastructure-only modules)# hidden: trueThe core_version_requirement: ^10 || ^11 declaration ensures this module installs on both Drupal 10 and Drupal 11 without modification. The ^ operator allows minor and patch updates within each major version.
Basic Module File (Optional)
Section titled “Basic Module File (Optional)”If your module needs only the library definitions and no custom hooks, you can skip the .module file entirely. Create it when you need hooks:
web/modules/custom/hx_library/hx_library.module:
<?php
/** * @file * HELiX Library module — provides HELiX web components as Drupal library assets. */
use Drupal\Core\Routing\RouteMatchInterface;
/** * Implements hook_help(). */function hx_library_help($route_name, RouteMatchInterface $route_match) { if ($route_name === 'help.page.hx_library') { $output = '<h3>' . t('About') . '</h3>'; $output .= '<p>' . t('Provides HELiX web components (@helixui/library v3.9.0) as Drupal library assets.') . '</p>'; $output .= '<h3>' . t('Usage') . '</h3>'; $output .= '<pre><code>'; $output .= "# In mytheme.info.yml\nlibraries:\n - hx_library/components\n\n"; $output .= "# In Twig\n{{ attach_library('hx_library/components') }}\n\n"; $output .= "# In render array\n\$build['#attached']['library'][] = 'hx_library/components';"; $output .= '</code></pre>'; return $output; }}Step 2: Define Libraries
Section titled “Step 2: Define Libraries”web/modules/custom/hx_library/hx_library.libraries.yml:
Full Bundle (Simplest Setup)
Section titled “Full Bundle (Simplest Setup)”# Full library bundle — loads all HELiX componentscomponents: version: 3.9.0 js: libraries/helix/dist/index.js: attributes: type: module preprocess: false minified: true css: theme: libraries/helix/dist/css/helix-all.css: {} dependencies: - core/onceUsage in a theme:
libraries: - hx_library/componentsUsage in a Twig template:
{{ attach_library('hx_library/components') }}Usage in a render array:
$build['#attached']['library'][] = 'hx_library/components';Per-Component Libraries (Granular Loading)
Section titled “Per-Component Libraries (Granular Loading)”For large sites where different page types use different components:
# Full bundlecomponents: version: 3.9.0 js: libraries/helix/dist/index.js: attributes: type: module preprocess: false minified: true
# Individual component librariesbutton: version: 3.9.0 js: libraries/helix/dist/components/hx-button/index.js: attributes: type: module preprocess: false minified: true
card: version: 3.9.0 js: libraries/helix/dist/components/hx-card/index.js: attributes: type: module preprocess: false minified: true
text-input: version: 3.9.0 js: libraries/helix/dist/components/hx-text-input/index.js: attributes: type: module preprocess: false minified: trueThis lets themes and templates attach only what they need:
// In a form preprocess hook — only load form inputs on form pagesfunction mytheme_preprocess_node__patient_form(&$variables) { $variables['#attached']['library'][] = 'hx_library/text-input'; $variables['#attached']['library'][] = 'hx_library/button';}Step 3: Populate the Library Files
Section titled “Step 3: Populate the Library Files”The module’s libraries/helix/dist/ directory must contain the actual HELiX JavaScript files. There are three strategies for populating this directory.
Strategy A: Manual Copy (Simplest)
Section titled “Strategy A: Manual Copy (Simplest)”Install HELiX locally and copy the built files:
# Create the target directorymkdir -p web/modules/custom/hx_library/libraries/helix
# Install HELiX in a temporary locationnpm install @helixui/library@3.9.0 --prefix /tmp/helix-install
# Copy the dist directorycp -r /tmp/helix-install/node_modules/@helixui/library/dist \ web/modules/custom/hx_library/libraries/helix/
# Verifyls web/modules/custom/hx_library/libraries/helix/dist/# Expected: index.js css/ components/Commit the copied files to your repository. This ensures consistent deployments without requiring npm at deploy time.
When to use: Environments without Composer expertise, small teams, or one-off deployments.
Strategy B: Composer + Asset Packagist
Section titled “Strategy B: Composer + Asset Packagist”Use Composer to manage the JavaScript dependency alongside your PHP packages.
Add Asset Packagist to composer.json:
{ "repositories": [ { "type": "composer", "url": "https://asset-packagist.org" } ]}Require the package:
composer require npm-asset/helixui--library:^3.9.0Configure installer path in composer.json:
{ "extra": { "installer-paths": { "web/modules/custom/hx_library/libraries/helix": [ "npm-asset/helixui--library" ] } }}After composer install, the HELiX dist files land at web/modules/custom/hx_library/libraries/helix/dist/index.js.
Updating:
composer require npm-asset/helixui--library:^3.9.0drush crWhen to use: Projects already using Composer for all dependency management, or organizations that want a single dependency manager for both PHP and JavaScript.
Strategy C: Build Pipeline Inside the Module
Section titled “Strategy C: Build Pipeline Inside the Module”Add a package.json to the module itself and run npm as part of module setup:
web/modules/custom/hx_library/package.json:
{ "name": "hx-library-drupal-module", "private": true, "scripts": { "sync": "npm install @helixui/library@3.9.0 && node scripts/copy-dist.js" }, "devDependencies": { "@helixui/library": "3.9.0" }}web/modules/custom/hx_library/scripts/copy-dist.js:
const { cpSync, mkdirSync } = require('fs');const { resolve } = require('path');
const src = resolve(__dirname, '../node_modules/@helixui/library/dist');const dest = resolve(__dirname, '../libraries/helix/dist');
mkdirSync(dest, { recursive: true });cpSync(src, dest, { recursive: true });
console.log('HELiX dist files synced to libraries/helix/dist/');Run once after module installation:
cd web/modules/custom/hx_librarynpm run syncCommit the resulting libraries/helix/dist/ files. The node_modules/ directory inside the module is not committed (add it to .gitignore).
Step 4: Enable the Module
Section titled “Step 4: Enable the Module”drush en hx_librarydrush crOr enable through the Drupal admin UI at Extend (/admin/modules).
Step 5: Attach Libraries in Themes
Section titled “Step 5: Attach Libraries in Themes”Global Attachment via Theme Info
Section titled “Global Attachment via Theme Info”name: My Themetype: themecore_version_requirement: ^10 || ^11base theme: stable9
libraries: - hx_library/componentsConditional Attachment via hook_page_attachments
Section titled “Conditional Attachment via hook_page_attachments”/** * Implements hook_page_attachments(). */function mytheme_page_attachments(array &$attachments) { // Load HELiX on all pages with node content. $node = \Drupal::routeMatch()->getParameter('node'); if ($node) { $attachments['#attached']['library'][] = 'hx_library/components'; }}Template-Level Attachment
Section titled “Template-Level Attachment”{# templates/node--article.html.twig #}
{{ attach_library('hx_library/components') }}
<article{{ attributes }}> <hx-card variant="featured"> <span slot="heading">{{ node.title.value }}</span> {{ content.body }} </hx-card></article>Step 6: Using Components in Templates
Section titled “Step 6: Using Components in Templates”{# Button with size attribute (hx-size, not size) #}<hx-button variant="primary" hx-size="lg">Submit</hx-button><hx-button variant="secondary" hx-size="md">Cancel</hx-button>
{# Card with hx-href — the whole card is the link. hx-card uses hx-href (not native href) so CMS preprocessors and template compilers leave the URL alone. The heading must be a semantic heading element, not a <span>. #}<hx-card variant="default" hx-href="{{ url('entity.node.canonical', {'node': node.id}) }}"> <h3 slot="heading">{{ node.title.value }}</h3> <p>{{ node.field_summary.value }}</p></hx-card>
{# Text input in a form #}<hx-text-input name="patient_name" label="Patient Name" required></hx-text-input>Key attribute notes:
- Use
hx-size(notsize) for component sizing - Use the
hrefattribute onhx-cardfor link card behavior; the card fireshx-clickon activation
Install and Update Hooks
Section titled “Install and Update Hooks”web/modules/custom/hx_library/hx_library.install:
<?php
/** * @file * Install, update, and uninstall functions for HELiX Library module. */
/** * Implements hook_install(). */function hx_library_install() { \Drupal::messenger()->addStatus( t('HELiX Library installed. Components available as hx_library/* Drupal libraries.') );}
/** * Implements hook_uninstall(). */function hx_library_uninstall() { \Drupal::configFactory() ->getEditable('hx_library.settings') ->delete();}
/** * Implements hook_requirements(). * * Verifies that the HELiX library files are present at the expected path. */function hx_library_requirements($phase) { $requirements = [];
if ($phase === 'runtime') { $module_path = \Drupal::service('extension.list.module')->getPath('hx_library'); $library_file = DRUPAL_ROOT . '/' . $module_path . '/libraries/helix/dist/index.js';
if (file_exists($library_file)) { $requirements['hx_library_files'] = [ 'title' => t('HELiX Library files'), 'value' => t('Present'), 'severity' => REQUIREMENT_OK, ]; } else { $requirements['hx_library_files'] = [ 'title' => t('HELiX Library files'), 'value' => t('Missing'), 'description' => t( 'HELiX component files not found at @path. Copy the files from @helixui/library@3.9.0/dist/ or run composer install.', ['@path' => $library_file] ), 'severity' => REQUIREMENT_ERROR, ]; } }
return $requirements;}
/** * Update HELiX library version to 3.9.0. */function hx_library_update_10001() { // Clear all caches to reload updated library definitions. drupal_flush_all_caches(); return t('HELiX Library updated to version 3.9.0. Cache cleared.');}The hook_requirements() implementation makes the status report at /admin/reports/status show a clear error if the library files are missing — a common issue after fresh deployments.
Configuration Management
Section titled “Configuration Management”Default Configuration
Section titled “Default Configuration”config/install/hx_library.settings.yml:
version: '3.9.0'use_cdn_fallback: falsecdn_url: 'https://cdn.jsdelivr.net/npm/@helixui/library'Config Schema
Section titled “Config Schema”config/schema/hx_library.schema.yml:
hx_library.settings: type: config_object label: 'HELiX Library Settings' mapping: version: type: string label: 'Library version' use_cdn_fallback: type: boolean label: 'Enable CDN fallback when local files missing' cdn_url: type: string label: 'CDN base URL'Export and import configuration across environments:
# Export on developmentdrush config:export
# Import on staging/productiondrush config:importMulti-Site Setup
Section titled “Multi-Site Setup”In a Drupal multi-site setup, you install the module once and enable it per site. All sites in the multi-site share the same module code and files, but each maintains its own configuration.
Shared Codebase Setup
Section titled “Shared Codebase Setup”docroot/├── sites/│ ├── site-a.example.com/│ │ └── settings.php # Site A configuration│ ├── site-b.example.com/│ │ └── settings.php # Site B configuration│ └── default/│ └── settings.php # Default/shared settings└── modules/ └── custom/ └── hx_library/ # Shared by all sitesEnable Per Site
Section titled “Enable Per Site”# Enable on Site Adrush -l https://site-a.example.com en hx_library
# Enable on Site Bdrush -l https://site-b.example.com en hx_librarySite-Specific Library Overrides with hook_library_info_alter
Section titled “Site-Specific Library Overrides with hook_library_info_alter”If different sites need different HELiX configurations (e.g., one site uses a CDN source, another uses local files):
/** * Implements hook_library_info_alter(). */function hx_library_library_info_alter(&$libraries, $extension) { if ($extension !== 'hx_library') { return; }
$config = \Drupal::config('hx_library.settings');
// Allow per-site CDN fallback via settings. if ($config->get('use_cdn_fallback')) { $version = $config->get('version') ?? '3.9.0'; $cdn_url = $config->get('cdn_url') ?? 'https://cdn.jsdelivr.net/npm/@helixui/library';
// Rewrite BOTH the JS entry AND the CSS entries — if you only // flip the JS to CDN and leave the CSS pointing at local files, // the theme will load the JS from jsDelivr but try to load the // stylesheets from local paths that may not exist on this // environment, leaving consumers with unstyled components. $libraries['components']['js'] = [ "{$cdn_url}@{$version}/dist/index.js" => [ 'type' => 'external', 'attributes' => ['type' => 'module'], 'preprocess' => FALSE, ], ];
$libraries['components']['css']['theme'] = [ "{$cdn_url}@{$version}/dist/css/helix-all.css" => [ 'type' => 'external', 'preprocess' => FALSE, ], ]; }}Override in a site-specific settings.php:
$config['hx_library.settings']['use_cdn_fallback'] = TRUE;$config['hx_library.settings']['version'] = '3.9.0';Drupal 10 and 11 Compatibility
Section titled “Drupal 10 and 11 Compatibility”HELiX itself has no Drupal PHP version dependencies — it is entirely a JavaScript/CSS library. Drupal-side compatibility comes from the module scaffolding:
| Feature | Drupal 10 | Drupal 11 |
|---|---|---|
core_version_requirement: ^10 || ^11 | Yes | Yes |
Libraries API (*.libraries.yml) | Yes | Yes |
attributes: { type: module } in libraries | Yes (since D9.3) | Yes |
hook_page_attachments() | Yes | Yes |
hook_library_info_alter() | Yes | Yes |
| Configuration management | Yes | Yes |
| Composer dependency management | Yes | Yes |
No conditional code or version detection is needed in the module. The same .info.yml, .libraries.yml, and hook implementations work identically on Drupal 10 and 11.
Troubleshooting
Section titled “Troubleshooting”Components not rendering — module enabled but nothing appears
Section titled “Components not rendering — module enabled but nothing appears”Check 1: Is the library attached?
# Inspect the rendered page source# Look for a <script type="module" src="/modules/custom/hx_library/...">If the script tag is missing, the library isn’t attached. Add {{ attach_library('hx_library/components') }} to a Twig template or attach it globally via mytheme.info.yml.
Check 2: Are the files present?
Visit /admin/reports/status — the hook_requirements() implementation shows an error if the dist files are missing.
Or check directly:
ls -la web/modules/custom/hx_library/libraries/helix/dist/# Should show index.jsCheck 3: Clear caches.
drush cr404 errors for index.js
Section titled “404 errors for index.js”Cause: The libraries/helix/dist/index.js file doesn’t exist at the path declared in .libraries.yml.
Fix: Verify the file path matches exactly. The library definition:
libraries/helix/dist/index.js:…resolves relative to the module directory (web/modules/custom/hx_library/), producing the full path web/modules/custom/hx_library/libraries/helix/dist/index.js.
Library files present but components unstyled
Section titled “Library files present but components unstyled”Cause: attributes: { type: module } is missing from the .libraries.yml definition.
Fix:
components: version: 3.9.0 js: libraries/helix/dist/index.js: attributes: type: module # This line is required preprocess: falseMulti-site: one site shows old version
Section titled “Multi-site: one site shows old version”Cause: Drupal caches library definitions per site. After updating the module files, each site’s cache must be cleared independently.
drush -l https://site-a.example.com crdrush -l https://site-b.example.com crUpdating HELiX in the Module
Section titled “Updating HELiX in the Module”When a new HELiX version is available:
- Update the library files in
libraries/helix/dist/using whichever strategy you chose (manual copy, Composer, or module build script) - Update the
versionfield inhx_library.libraries.ymlto match the new version - Add an update hook in
hx_library.installto clear caches - Run
drush updatedb && drush cron each site
/** * Update HELiX library files to version 3.9.0. */function hx_library_update_10002() { drupal_flush_all_caches(); return t('HELiX Library updated to version 3.9.0.');}Production Checklist
Section titled “Production Checklist”- Module enabled on all target sites:
drush en hx_library -
libraries/helix/dist/index.jsfile present -
core_version_requirement: ^10 || ^11in.info.yml -
attributes: { type: module }in.libraries.ymlfor each JS entry -
hook_requirements()reports OK on/admin/reports/status - Library attached in at least one theme (
mytheme.info.ymlor template) - Components render with Shadow DOM in browser DevTools
- Configuration exported and committed:
drush config:export - Multi-site caches cleared on all sites after updates
- Update hooks added for future library version upgrades
Next Steps
Section titled “Next Steps”- CDN Installation — Simpler approach if multi-site distribution is not required
- npm Installation — Per-theme build pipeline for maximum optimization
- Getting Started — Decision guide for choosing between all three approaches