Skip to content
HELiX

Plain HTML / CDN

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

HELIX components work in any HTML page with a single <script> tag. No build tool, no npm, no bundler required.

A bare specifier like import '@helixui/library' only resolves in the browser if an import map is in scope — otherwise the browser throws a module-resolution error. Use a pinned, version-locked CDN URL plus an import map for the library’s bare dependencies (lit, lit/*, @helixui/tokens, @helixui/icons):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
<script type="importmap">
{
"imports": {
"@helixui/library": "https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/index.js",
"@helixui/tokens": "https://cdn.jsdelivr.net/npm/@helixui/tokens@3.9.0/dist/index.js",
"@helixui/icons": "https://cdn.jsdelivr.net/npm/@helixui/icons@1.0.0/dist/index.js",
"lit": "https://cdn.jsdelivr.net/npm/lit@3/+esm",
"lit/": "https://cdn.jsdelivr.net/npm/lit@3/"
}
}
</script>
</head>
<body>
<hx-button variant="primary" id="my-btn">Click me</hx-button>
<script type="module">
import '@helixui/library';
</script>
</body>
</html>

Pin the version explicitly — @latest will silently roll forward across breaking changes. The version range above is illustrative; pick whatever matches your tested baseline.

For performance, load only the components you use. Per-component entry points resolve to @helixui/library/components/<name> (subpath export → dist/components/<name>/index.js):

<script type="importmap">
{
"imports": {
"@helixui/library/components/hx-button": "https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/components/hx-button/index.js",
"@helixui/library/components/hx-text-input": "https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/components/hx-text-input/index.js",
"@helixui/tokens": "https://cdn.jsdelivr.net/npm/@helixui/tokens@3.9.0/dist/index.js",
"@helixui/icons": "https://cdn.jsdelivr.net/npm/@helixui/icons@1.0.0/dist/index.js",
"lit": "https://cdn.jsdelivr.net/npm/lit@3/+esm",
"lit/": "https://cdn.jsdelivr.net/npm/lit@3/"
}
}
</script>
<script type="module">
import '@helixui/library/components/hx-button';
import '@helixui/library/components/hx-text-input';
</script>

Listen for HELIX’s hx- prefixed custom events with standard addEventListener:

<hx-button id="save-btn" variant="primary">Save</hx-button>
<hx-text-input id="name-input" name="name" placeholder="Enter name"></hx-text-input>
<script type="module">
import '@helixui/library';
const btn = document.getElementById('save-btn');
const input = document.getElementById('name-input');
btn.addEventListener('hx-click', (event) => {
console.log('Button clicked', event);
});
input.addEventListener('hx-input', (event) => {
console.log('Input value:', event.target.value);
});
input.addEventListener('hx-change', (event) => {
console.log('Committed value:', event.target.value);
});
</script>

HELIX components expose DOM properties for dynamic values. Set them directly on the element reference:

<hx-button id="submit-btn" variant="primary">Submit</hx-button>
<script type="module">
import '@helixui/library';
const btn = document.getElementById('submit-btn');
// Set property
btn.disabled = true;
btn.loading = true;
// Read property
console.log(btn.variant); // 'primary'
</script>

Follow HTML’s boolean attribute rules — presence means true, absence means false. See Boolean Attributes for details.

<!-- Disabled: attribute present -->
<hx-button disabled>Disabled</hx-button>
<!-- Enabled: attribute absent -->
<hx-button>Enabled</hx-button>
<!-- Wrong: string "false" still disables -->
<hx-button disabled="false">Still disabled!</hx-button>

Toggle in JavaScript:

// Disable
btn.setAttribute('disabled', '');
// or:
btn.disabled = true;
// Enable
btn.removeAttribute('disabled');
// or:
btn.disabled = false;

HELIX form components work with native <form> elements and FormData:

<form id="contact-form">
<hx-text-input name="email" type="email" required placeholder="you@example.com"></hx-text-input>
<hx-select name="subject">
<option value="">Choose a subject</option>
<option value="support">Support</option>
<option value="billing">Billing</option>
</hx-select>
<hx-button type="submit" variant="primary">Send</hx-button>
</form>
<script type="module">
import '@helixui/library';
const form = document.getElementById('contact-form');
form.addEventListener('submit', (event) => {
event.preventDefault();
const data = new FormData(form);
console.log({
email: data.get('email'),
subject: data.get('subject'),
});
});
</script>

If you query an element immediately and it hasn’t registered yet, customElements.whenDefined() ensures it’s ready:

await customElements.whenDefined('hx-button');
const btn = document.querySelector('hx-button');
btn.disabled = false;

Or use type="module" scripts — they execute after the document is parsed and deferred, so custom elements registered in the same module will be defined before your listener code runs.

A complete, self-contained example:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>HELIX Demo</title>
</head>
<body>
<form
id="login-form"
style="display: flex; flex-direction: column; gap: 1rem; max-width: 400px; padding: 2rem;"
>
<hx-text-input
id="email"
name="email"
type="email"
required
placeholder="Email address"
></hx-text-input>
<hx-text-input
id="password"
name="password"
type="password"
required
placeholder="Password"
></hx-text-input>
<hx-button id="login-btn" type="submit" variant="primary">Sign in</hx-button>
</form>
<div id="output"></div>
<script type="module">
import '@helixui/library';
const form = document.getElementById('login-form');
const output = document.getElementById('output');
form.addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(form);
output.textContent = `Logging in as ${data.get('email')}...`;
});
</script>
</body>
</html>