npm Installation
apps/docs/src/content/docs/drupal/installation/npm Click to copy apps/docs/src/content/docs/drupal/installation/npm npm installation integrates HELiX directly into your Drupal theme’s JavaScript build pipeline. The bundler compiles HELiX alongside your theme’s own code and outputs optimized local assets. Drupal loads those assets the same way it loads any other theme JavaScript — as local files with the Libraries API.
This is the recommended approach for production custom themes where performance, security, offline development, and long-term maintainability are priorities.
Why Choose npm Installation
Section titled “Why Choose npm Installation”Complete Local Control
Section titled “Complete Local Control”Your site has zero runtime dependency on external services. All assets live in your repository (or are generated at deploy time from committed package-lock.json). Corporate firewalls, CDN outages, and HIPAA restrictions do not apply.
Tree-Shaking
Section titled “Tree-Shaking”Import only the components your theme actually uses. If your site needs ten of the 81 components the library ships, only those ten (plus shared base modules) reach the browser. The payload savings depend on which components you import — see packages/hx-library/bundle-budgets.json for per-component budgets — but trimming to the components you actually use is the single biggest reduction available on top of CDN loading.
Deterministic Versions
Section titled “Deterministic Versions”package-lock.json pins every dependency to an exact resolved version. Development, staging, and production all run identical code — no surprises from CDN auto-updates.
Drupal Asset Aggregation
Section titled “Drupal Asset Aggregation”Local JavaScript files participate in Drupal’s asset aggregation system. Drupal can combine your theme’s HELiX-importing entry point with other scripts, reducing HTTP requests.
Prerequisites
Section titled “Prerequisites”Before starting, verify your environment has:
- Node.js 22 LTS or Node.js 24 —
node --version(Node 18 and Node 20 are no longer supported) - npm 9 or later —
npm --version(comes with Node.js) - A Drupal 10 or 11 site with a custom theme
- Your theme located at
web/themes/custom/mytheme/
If Node.js is not installed, download it from nodejs.org or use a version manager like nvm.
Step 1: Initialize Theme package.json
Section titled “Step 1: Initialize Theme package.json”If your theme doesn’t already have a package.json, create one:
cd web/themes/custom/mythemenpm init -yThis creates a minimal package.json. You’ll customize it in a moment.
Step 2: Install HELiX
Section titled “Step 2: Install HELiX”# @helixui/library declares @helixui/icons + @floating-ui/dom as peer# dependencies and bundles @helixui/tokens + lit as regular dependencies.# Install the library together with its peer deps:npm install @helixui/library@3.9.0 @helixui/icons @floating-ui/domIf your theme needs to consume HELiX CSS custom properties outside of components (raw token CSS, brand registry, scoped overrides), install the tokens package explicitly:
npm install @helixui/tokens@3.9.0Verify the installation:
ls node_modules/@helixui/library/dist/# Should show: index.js components/ css/ ...The library’s main entry point is ./dist/index.js as declared in the package’s main field.
Step 3: Create the JavaScript Entry Point
Section titled “Step 3: Create the JavaScript Entry Point”Create a JavaScript file that imports the HELiX components your theme needs.
Import Full Library
Section titled “Import Full Library”// Import entire HELiX library — all components registeredimport '@helixui/library';Import Specific Components (Tree-Shaking)
Section titled “Import Specific Components (Tree-Shaking)”// Import only the components this theme usesimport '@helixui/library/components/hx-button';import '@helixui/library/components/hx-card';import '@helixui/library/components/hx-text-input';import '@helixui/library/components/hx-select';import '@helixui/library/components/hx-badge';Tree-shaking at import granularity is the most effective approach when you’re using five or fewer components. For sites using the majority of the library, importing the full library is simpler and the size difference is minimal.
Combining with Theme JavaScript
Section titled “Combining with Theme JavaScript”// HELiX componentsimport '@helixui/library';
// Theme behaviorsimport './behaviors/navigation.js';import './behaviors/search.js';import './behaviors/forms.js';Step 4: Configure the Bundler
Section titled “Step 4: Configure the Bundler”Option A: Vite (Recommended)
Section titled “Option A: Vite (Recommended)”Vite is the preferred build tool for new Drupal theme projects. It’s fast, has excellent ES module support, and requires minimal configuration.
Install Vite:
npm install --save-dev vite@^6.0.0vite.config.js:
import { defineConfig } from 'vite';import { resolve } from 'path';
export default defineConfig({ build: { // Output to theme's dist directory outDir: 'dist/js',
// Generate source maps for debugging sourcemap: true,
lib: { entry: resolve(__dirname, 'src/js/theme.js'), formats: ['es'], fileName: 'theme', },
rollupOptions: { output: { // Separate HELiX into its own chunk for better caching manualChunks: { helix: ['@helixui/library'], }, }, },
// Disable minification in development for readability minify: process.env.NODE_ENV === 'production' ? 'esbuild' : false, },});With manualChunks, Vite produces two output files:
dist/js/helix.js— The HELiX library chunkdist/js/theme.js— Your theme code that imports the HELiX chunk
This separation allows browsers to cache HELiX independently of your theme code. When you update theme behavior files, users don’t re-download the component library.
package.json scripts:
{ "name": "mytheme", "private": true, "scripts": { "dev": "vite build --watch", "build": "vite build", "build:prod": "NODE_ENV=production vite build" }, "dependencies": { "@helixui/library": "3.9.0" }, "devDependencies": { "vite": "^6.0.0" }}Use exact versions ("3.9.0" not "^3.9.0") for @helixui/library in production themes. This ensures identical bundles across environments.
Option B: Webpack
Section titled “Option B: Webpack”For themes with existing Webpack configurations:
Install Webpack:
npm install --save-dev webpack@^5.0.0 webpack-cli@^5.0.0webpack.config.js:
const path = require('path');
module.exports = { entry: './src/js/theme.js',
output: { filename: 'theme.js', path: path.resolve(__dirname, 'dist/js'), // Required for ES module output module: true, library: { type: 'module', }, },
// Enable ES module output experiments: { outputModule: true, },
optimization: { splitChunks: { cacheGroups: { // Split HELiX into separate chunk helix: { test: /[\\/]node_modules[\\/]@helixui[\\/]/, name: 'helix', chunks: 'all', }, }, }, },
mode: process.env.NODE_ENV || 'development', devtool: 'source-map',};package.json scripts with Webpack:
{ "scripts": { "dev": "webpack --watch --mode development", "build": "webpack --mode production" }}Important note on Webpack and ES modules: HELiX components use ES module syntax (import/export) throughout. Webpack’s experiments.outputModule: true is required to produce an output file that Drupal can load as a type="module" script. Without this, Webpack wraps the output in a CommonJS wrapper that is incompatible with the browser’s native ES module loader.
Step 5: Build the Assets
Section titled “Step 5: Build the Assets”# Development build with watch mode (rebuilds on file changes)npm run dev
# Production build (optimized)npm run build:prodVerify the output:
ls -lh dist/js/# Expected output (Vite with manualChunks):# helix.js — Full HELiX library chunk (see packages/hx-library/bundle-budgets.json# for the per-component + total bundle ceilings enforced in CI;# the full bundle target is ≤200 KB gzipped, with most production# themes landing well below that after tree-shaking unused components)# theme.js — Your theme code (~small)Step 6: Define Drupal Libraries
Section titled “Step 6: Define Drupal Libraries”Add the compiled assets to your theme’s .libraries.yml:
global: version: 1.x js: # Import map entry for HELiX chunk (if using manualChunks) dist/js/helix.js: attributes: type: module preprocess: false minified: true # Theme entry point dist/js/theme.js: attributes: type: module preprocess: false minified: true dependencies: - core/drupal - core/onceIf you did not use manualChunks and Vite produced a single output file:
global: version: 1.x js: dist/js/theme.js: attributes: type: module preprocess: false minified: true dependencies: - core/drupal - core/onceThe attributes: { type: module } Requirement
Section titled “The attributes: { type: module } Requirement”This attribute is mandatory. HELiX components are ES modules — they use top-level import and export statements. A browser loading an ES module file as a classic script (without type="module") will throw a syntax error. Drupal does not add type="module" automatically; you must declare it in .libraries.yml.
The preprocess: false Setting
Section titled “The preprocess: false Setting”Drupal’s asset preprocessor appends query strings and can attempt to aggregate external references. Setting preprocess: false on each JS file prevents unexpected behavior with ES module output. For local files this is optional but recommended when using type: module.
Step 7: Attach the Library
Section titled “Step 7: Attach the Library”Global (Every Page)
Section titled “Global (Every Page)”name: My Healthcare Themetype: themecore_version_requirement: ^10 || ^11base theme: stable9
libraries: - mytheme/globalPer Template
Section titled “Per Template”{# templates/node--article.html.twig #}
{{ attach_library('mytheme/global') }}
<article{{ attributes }}> ...</article>Step 8: Use Components in Templates
Section titled “Step 8: Use Components in Templates”{# Article node template #}
<article{{ attributes }}> <hx-card variant="featured"> <h3 slot="heading">{{ node.title.value }}</h3> {{ content.body }} <div slot="actions"> <hx-button variant="primary" hx-size="sm"> Read More </hx-button> </div> </hx-card></article>For link cards, use the hx-href attribute (NOT native href). hx-card is an autonomous custom
element on a non-anchor host; the hx- prefix prevents CMS preprocessors from rewriting the URL.
Do not combine hx-href with slot="actions" — that nests an interactive control inside the link
and breaks the activation contract:
<hx-card variant="default" hx-href="{{ url('entity.node.canonical', {'node': node.id}) }}"> <h3 slot="heading">{{ node.title.value }}</h3> <p>{{ node.field_teaser.value }}</p></hx-card>For sized buttons, use hx-size (not size):
<hx-button variant="primary" hx-size="lg">Submit</hx-button><hx-button variant="secondary" hx-size="md">Cancel</hx-button>Deployment Pipeline
Section titled “Deployment Pipeline”Your deployment process must run the npm build before deploying theme files.
Basic Deployment Script
Section titled “Basic Deployment Script”#!/bin/bashset -e
echo "Installing theme dependencies..."cd web/themes/custom/mythemenpm ci --production=false
echo "Building theme assets..."npm run build:prod
echo "Deploying to server..."rsync -az --exclude=node_modules . user@server:/var/www/html/web/themes/custom/mytheme/
echo "Clearing Drupal caches..."ssh user@server 'drush -r /var/www/html cr'
echo "Deployment complete."CI/CD (GitHub Actions Example)
Section titled “CI/CD (GitHub Actions Example)”name: Deploy Drupal
on: push: branches: [main]
jobs: build-and-deploy: runs-on: ubuntu-latest
steps: - uses: actions/checkout@v4
- name: Use Node.js 22 uses: actions/setup-node@v4 with: node-version: '22' cache: 'npm' cache-dependency-path: web/themes/custom/mytheme/package-lock.json
- name: Install theme dependencies run: npm ci working-directory: web/themes/custom/mytheme
- name: Build theme assets run: npm run build:prod working-directory: web/themes/custom/mytheme
- name: Deploy # ... your deployment stepKey points:
- Use
npm ci(notnpm install) in CI. It installs exactly what’s inpackage-lock.jsonand fails if the lockfile is out of date. - Cache the
node_modulesdirectory using the lockfile hash for faster CI runs. - Never deploy
node_modulesto the server — only deploy compileddist/assets.
Updating HELiX
Section titled “Updating HELiX”cd web/themes/custom/mytheme
# Install the new versionnpm install @helixui/library@3.9.0
# Review the HELiX changelog before rebuilding
# Rebuild assetsnpm run build:prod
# Verify nothing broke (run your visual regression / accessibility tests)
# Commit the updated package-lock.json and built assetsgit add package.json package-lock.json dist/git commit -m "chore(theme): update @helixui/library to 3.9.0"Always review the HELiX changelog before updating. Check for attribute name changes, removed components, or slot renames that would require template updates.
Source Maps
Section titled “Source Maps”Source maps let you debug the original component source in browser DevTools even when loading a compiled bundle.
// vite.config.js — enable in both dev and prodexport default defineConfig({ build: { sourcemap: true, // Generates .js.map files alongside .js // ... },});When sourcemap: true, Vite generates dist/js/theme.js.map. Browsers use this automatically. No Drupal configuration is needed — source maps are fetched by the browser’s DevTools on demand, not loaded by page visitors.
Handling CSS
Section titled “Handling CSS”HELiX components encapsulate their styles in Shadow DOM. They do not require you to load a separate CSS file for the component rendering to work.
If you want to use HELiX design token CSS custom properties in your own theme CSS (outside of components), import the tokens:
npm install @helixui/tokens@3.9.0import '@helixui/tokens/tokens.css';import '@helixui/library';Or import the tokens CSS directly in your theme’s SCSS/CSS:
@import '@helixui/tokens/tokens.css';Add the compiled CSS to your Drupal library definition:
global: version: 1.x css: theme: dist/css/theme.css: {} js: dist/js/theme.js: attributes: type: module preprocess: falseTroubleshooting
Section titled “Troubleshooting”import statement syntax error in browser console
Section titled “import statement syntax error in browser console”Cause: The compiled output file is being loaded as a classic script, not as an ES module.
Fix: Ensure attributes: { type: module } is in your .libraries.yml entry for the JS file.
Components registered but no styles (flat HTML appearance)
Section titled “Components registered but no styles (flat HTML appearance)”Cause: Components rely on Constructable Stylesheets / Shadow DOM — if you’re testing in a very old browser (pre-2020), this won’t work. All modern browsers are fine.
Cause (alternate): Design tokens CSS not loaded.
Fix: Import or link @helixui/tokens as described in the CSS section above.
Build produces empty output or missing HELiX code
Section titled “Build produces empty output or missing HELiX code”Cause: Tree-shaking removed HELiX because the imports weren’t detected as side-effectful.
Fix: Add a sideEffects exception in your package.json or ensure you’re importing from the main package entry:
// Prefer this (uses package.json main entry, marked as sideEffect)import '@helixui/library';
// Or this per-component side-effect import (preferred for tree-shaking;// @helixui/library exposes `./components/<tag>` subpaths in its exports// map). The class `HelixButton` (not `HxButton`) is what's exported as// a class; HxButton is not a class type — it lives under Hx*Detail// event-type aliases only.// import '@helixui/library/components/hx-button';drush cr needed after every build
Section titled “drush cr needed after every build”This is expected. After rebuilding theme assets and deploying, clear Drupal’s asset caches so it picks up new file hashes:
drush crIn local development with npm run dev (watch mode), clear caches once after setting up the library. Subsequent rebuilds don’t require cache clearing unless you changed .libraries.yml.
Production Checklist
Section titled “Production Checklist”-
npm ciruns in CI pipeline before build -
npm run build:prodruns withNODE_ENV=production - Compiled
dist/files are deployed to the server -
node_modules/is never deployed (only used at build time) -
attributes: { type: module }present in.libraries.ymlfor all JS entries -
package-lock.jsonis committed to source control - Exact version pinned in
package.json("3.9.0"not"^3.9.0") - Source maps generated for production debugging
-
drush crruns post-deployment - Components tested in target browsers after deployment
Next Steps
Section titled “Next Steps”- CDN Installation — Simpler setup if a build pipeline is not practical
- Module Installation — Package the built assets in a Drupal module for multi-site reuse