Troubleshooting Hub
apps/docs/src/content/docs/guides/troubleshooting Click to copy apps/docs/src/content/docs/guides/troubleshooting Troubleshooting Hub
Section titled “Troubleshooting Hub”Quick reference for common issues when building with HELiX components. Find your category below, or search for a specific error message.
Installation & Setup
Section titled “Installation & Setup”Component not defined / hx-button is not a valid custom element
Section titled “Component not defined / hx-button is not a valid custom element”The component script hasn’t loaded before the element is parsed.
<!-- Wrong: element appears before the script --><hx-button>Click me</hx-button><script type="module" src="hx-button.js"></script>
<!-- Correct: script loads first, or use defer --><script type="module" src="hx-button.js"></script><hx-button>Click me</hx-button>Or use customElements.whenDefined() to await registration:
await customElements.whenDefined('hx-button');const btn = document.querySelector('hx-button');Tree-shaking not working — full bundle included
Section titled “Tree-shaking not working — full bundle included”Import from the per-component entry point, not the barrel:
// Wrong: imports the entire libraryimport '@helixui/library';
// Correct: only loads hx-buttonimport '@helixui/library/components/hx-button';TypeScript: Property 'X' does not exist on type 'HTMLElement'
Section titled “TypeScript: Property 'X' does not exist on type 'HTMLElement'”Add the HELiX type declarations to your tsconfig.json:
{ "compilerOptions": { "types": ["@helixui/library"] }}Or import the component type directly:
import type { HelixButton } from '@helixui/library';const btn = document.querySelector('hx-button') as HelixButton;Note: HELiX exports the class as HelixButton (and HelixTextInput, HelixCard, etc.) from the package root, not HxButton from the per-component subpath.
Shadow DOM Styling
Section titled “Shadow DOM Styling”CSS styles not applying to a component
Section titled “CSS styles not applying to a component”External styles cannot pierce Shadow DOM boundaries. Use the supported styling APIs:
/* Wrong: descendant selector can't reach inside shadow root */hx-button span { color: red; }
/* Correct 1: CSS custom properties (design tokens) */hx-button { --hx-button-bg: red; }
/* Correct 2: ::part() pseudo-element */hx-button::part(button) { background: red; }::part() selector not working
Section titled “::part() selector not working”Check the component’s documentation for the exact part name. Part names are case-sensitive and hyphenated:
/* Wrong */hx-button::part(Button) { ... }
/* Correct */hx-button::part(button) { ... }Part names for every component are listed in the component’s API reference page.
Inheritable CSS not reaching inside the component
Section titled “Inheritable CSS not reaching inside the component”Shadow DOM inherits a subset of CSS properties — primarily typography and color. Layout and box-model properties do not inherit. For typography inheritance:
/* This DOES work — font properties are inherited */hx-button { font-family: 'Inter', sans-serif; }
/* This does NOT work — background is not inherited */hx-button { background: blue; } /* applies to host, not internals */:host styles seem to have no effect
Section titled “:host styles seem to have no effect”:host styles apply to the custom element itself (the host element). They have lower specificity than styles applied from outside:
/* In your app CSS — this overrides :host styles */hx-button { display: block; }If you’re authoring a component, use :host in the component’s shadow styles. If you’re consuming a component, apply styles directly to the element selector.
Form Integration
Section titled “Form Integration”Form submission not including component values
Section titled “Form submission not including component values”HELiX form components use ElementInternals for form association. Form-associated custom elements retain their form association when nested inside wrapper elements (like <div>) as long as they are inside a <form> or linked via the form attribute. Ensure the component has a name attribute for form submission:
<!-- Wrong: name attribute missing --><form> <hx-text-input></hx-text-input></form>
<!-- Correct --><form> <hx-text-input name="email" type="email"></hx-text-input></form>FormData doesn’t contain the component’s value
Section titled “FormData doesn’t contain the component’s value”HELiX form components call internals.setFormValue() internally via the shared FormMixin (packages/hx-library/src/mixins/). static formAssociated = true is set on the class but isn’t surfaced through CEM as a normal member — check the component’s source directly, or the form-associated component list for the canonical inventory. If a component you’re using isn’t form-associated, it won’t participate in native form submission.
For unsupported components, use a hidden <input> and sync it manually:
const hxSelect = document.querySelector('hx-select');const hidden = document.querySelector('input[name="myField"]');
hxSelect.addEventListener('hx-change', (e) => { hidden.value = e.detail.value;});Validation not triggering on submit
Section titled “Validation not triggering on submit”HELiX form components use the Constraint Validation API. Call reportValidity() on the form to trigger validation display:
const form = document.querySelector('form');form.addEventListener('submit', (e) => { if (!form.reportValidity()) { e.preventDefault(); }});For programmatic validation state, see Form Validation.
ElementInternals errors in older browsers
Section titled “ElementInternals errors in older browsers”ElementInternals requires Chrome 77+, Firefox 93+, and Safari 16.4+. HELiX targets evergreen-browser releases (Chrome / Edge / Firefox latest two majors and Safari 16+), so the API is available everywhere HELiX supports.
If you need to support a strictly older browser matrix outside HELiX’s official support window, the element-internals-polyfill is a third-party option — it is not bundled or recommended by HELiX, and ships its own correctness/feature caveats. Use at your own risk:
import 'element-internals-polyfill';Event Handling
Section titled “Event Handling”Custom events not bubbling outside the component
Section titled “Custom events not bubbling outside the component”HELiX events use bubbles: true, composed: true so they cross shadow boundaries. If you’re not receiving events on a parent element, verify:
- The event name — HELiX events use the
hx-prefix (e.g.,hx-change,hx-click) - The event listener is on an ancestor element, not inside a shadow root
- You’re not calling
e.stopPropagation()somewhere in the tree
// Correct: listen on any ancestordocument.addEventListener('hx-change', (e) => { console.log(e.target, e.detail);});Event fires but e.target is not the component
Section titled “Event fires but e.target is not the component”When an event crosses a shadow root, e.target is retargeted to the host element from the perspective of the listener. This is expected shadow DOM behavior:
// Inside shadow root: target = internal <input>// Outside shadow root: target = hx-text-input (host)hxInput.addEventListener('hx-change', (e) => { console.log(e.target); // hx-text-input ✓});Use e.composedPath() to inspect the full path including shadow internals.
Event not firing at expected time
Section titled “Event not firing at expected time”Check if the component dispatches the event synchronously or asynchronously. Components that await user interaction (e.g., hx-change on blur vs. hx-input on keypress) may fire at different times. Review the component’s API reference for event timing details.
SSR / Hydration
Section titled “SSR / Hydration”document is not defined during SSR
Section titled “document is not defined during SSR”HELiX components are browser-only — they rely on the DOM. Do not import component definitions in server-side render paths:
// Wrong: runs during SSRimport '@helixui/library/components/hx-button';
// Correct: dynamic import on client onlyif (typeof window !== 'undefined') { await import('@helixui/library/components/hx-button');}In Astro, custom elements don’t need a client:only directive (that’s for framework components like React or Svelte). Instead, import the component script in a <script> tag:
<hx-button>Click me</hx-button>
<script> import '@helixui/library/components/hx-button';</script>Hydration mismatch warnings
Section titled “Hydration mismatch warnings”Web components render their shadow DOM on the client. Server-rendered HTML will not include the shadow DOM content, causing hydration mismatches in frameworks that compare server vs client output.
Avoid rendering component internals server-side. The custom element tag itself (<hx-button>) is safe to include in server HTML — the shadow DOM is created on the client when the element upgrades. Import the component JS in a client-side <script> tag.
Component appears unstyled / FOUC (Flash of Unstyled Content)
Section titled “Component appears unstyled / FOUC (Flash of Unstyled Content)”Components may appear unstyled briefly before the JavaScript loads and upgrades them. Mitigate with CSS:
/* Hide unupgraded elements */hx-button:not(:defined) { visibility: hidden;}Or use customElements.whenDefined() to show content only after upgrade:
await Promise.allSettled([ customElements.whenDefined('hx-button'), customElements.whenDefined('hx-text-input'),]);document.body.classList.add('components-ready');TypeScript
Section titled “TypeScript”JSX.IntrinsicElements errors with custom elements in React
Section titled “JSX.IntrinsicElements errors with custom elements in React”Add HELiX element types to your React type declarations:
import type { HelixButton } from '@helixui/library';
declare global { namespace JSX { interface IntrinsicElements { 'hx-button': React.DetailedHTMLProps< React.HTMLAttributes<HelixButton> & Partial<HelixButton>, HelixButton >; } }}If you’re building a React app, prefer @helixui/react — its generated wrappers ship with React-friendly types out of the box and avoid the manual IntrinsicElements declaration.
Generic component props not resolving correctly
Section titled “Generic component props not resolving correctly”HELiX components use TypeScript strict mode. If a prop type appears as never or doesn’t match, verify you’re importing the component class from the package root and using the correct property name (camelCase in TypeScript, kebab-case in HTML):
import type { HelixSelect } from '@helixui/library';// Property: el.value (TS) → attribute: value (HTML)// hx-select is single-selection; for multi-select use the dedicated combobox.@property decorator type errors
Section titled “@property decorator type errors”When extending HELiX components, ensure your tsconfig.json includes the experimentalDecorators and useDefineForClassFields: false settings required for Lit:
{ "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false }}Bundle Size
Section titled “Bundle Size”Bundle size is larger than expected
Section titled “Bundle size is larger than expected”- Verify you’re using per-component imports (not the barrel export)
- Check for accidental side-effect imports
- Run bundle analysis to find the source:
npx vite-bundle-visualizer# ornpx webpack-bundle-analyzer stats.jsonDesign tokens CSS file is large
Section titled “Design tokens CSS file is large”The token CSS file (packages/hx-tokens/dist/) emits the primitive and semantic palette layers that every component depends on; per-component CSS tokens are defined in each component’s *.styles.ts and ship as part of the component bundle, not in the token CSS file. There isn’t a “minimal token build” toggle today — for production, lean on the per-component imports (each component pulls only the tokens it actually consumes) and let the consumer bundler dedupe.
Lit is bundled multiple times
Section titled “Lit is bundled multiple times”If you use multiple HELiX components and Lit appears duplicated in your bundle, ensure all components resolve to the same lit package version. Check for version conflicts:
npm ls lit# orpnpm why litAll HELiX components depend on Lit. @helixui/library declares lit as a direct dependency (not a peer), so the version is pinned by the package — duplicate copies of Lit in your bundle usually come from a separate lit install in your app or another library, not from HELiX itself.
Drupal Integration
Section titled “Drupal Integration”Component not upgrading in Drupal
Section titled “Component not upgrading in Drupal”Drupal’s AJAX system may inject HTML after the initial page load. Components inserted via AJAX need the library to already be loaded. Ensure the HELiX library attachment is global (not lazy):
my-helix-components: js: js/my-helix-components.js: {} dependencies: - core/drupal - core/onceAttach the library in a hook so it loads on every page that might use AJAX to inject components:
function my_module_page_attachments(array &$attachments) { $attachments['#attached']['library'][] = 'my_module/my-helix-components';}Drupal Behaviors not re-attaching to upgraded components
Section titled “Drupal Behaviors not re-attaching to upgraded components”The customElements.whenDefined() check inside your behavior ensures the component’s API is available before use:
Drupal.behaviors.myComponent = { attach(context) { once('my-component', 'hx-button[data-action]', context).forEach((el) => { customElements.whenDefined('hx-button').then(() => { el.addEventListener('hx-click', handleAction); }); }); },};Twig template rendering attribute as "false" string
Section titled “Twig template rendering attribute as "false" string”Boolean attributes in HTML are presence-based — the string "false" is truthy. Use conditional Twig syntax:
{# Wrong: renders disabled="false" which still disables the button #}<hx-button disabled="{{ disabled }}">Submit</hx-button>
{# Correct: only renders the attribute when true #}<hx-button {% if disabled %}disabled{% endif %}>Submit</hx-button>See Boolean Attributes for full details.
Component styles conflict with Drupal theme CSS
Section titled “Component styles conflict with Drupal theme CSS”Shadow DOM prevents most conflicts, but global CSS resets can affect the host element. Scope your resets to avoid targeting custom elements:
/* Wrong: affects hx-button host element */* { box-sizing: border-box; margin: 0; }
/* Better: scope reset to standard elements only */html, body, div, span, p, a, ul, ol, li,h1, h2, h3, h4, h5, h6, img,button, input, textarea, select, table { box-sizing: border-box;}For complete Drupal integration guidance, see Drupal Integration Overview.
Still Stuck?
Section titled “Still Stuck?”If your issue isn’t covered here:
- GitHub Issues: Search existing issues or file a new one with a minimal reproduction
- Drupal-specific issues: Include your Drupal version, theme, and the HELiX library attachment method
- Component API reference: Each component page lists all properties, events, slots, and CSS parts
- Architecture docs: Light DOM ADR, Slots vs Props ADR, and Design Token Tiers cover the foundational concepts