Skip to content
Alpha

Hololink UI is under active development. Components, APIs and these docs may change without notice.

Usage with frameworks

Hololink UI components are standard custom elements, so every framework can render them. The differences are small: how you tell the compiler that holo-* tags are not Vue or Angular components, and how you reach properties and events. This page is a how-to for each setup. For installation and the .npmrc setup, see Installation.

All examples assume you have loaded the global stylesheet once at your entry point:

import '@philbob-sideprojects/hololink-ui/styles/global.css';

Registering. Import the entry point for each component. Importing it defines the element.

import '@philbob-sideprojects/hololink-ui/holo-button';
import '@philbob-sideprojects/hololink-ui/holo-text-field';

Attributes and properties. Strings, numbers and booleans can be set as attributes (label="Save", disabled). Set non-string values, or values that change at runtime, as DOM properties (el.value = 'x', el.checked = true).

Events. Components dispatch CustomEvents with camelCase names prefixed holo. The payload is in event.detail. Because the names are camelCase, listen with addEventListener('holoChange', ...). Many frameworks lower-case or hyphenate event names in templates, so addEventListener is the reliable option when a template binding does not fire.

Event Emitted by detail
holoInput holo-text-field, holo-textarea (string), holo-slider (number) The current value
holoChange holo-text-field, holo-textarea, holo-select, holo-radio-group The value (string)
holoChange holo-slider The value (number)
holoChange holo-checkbox, holo-switch checked (boolean)
holoOpen, holoClose holo-dialog, holo-drawer, holo-popover, holo-menu None
holoMenuSelect holo-menu The selected value (string)
holoTabChange holo-tabs { value: string, index: number }
holoPageChange holo-pagination The page number
holoToggle holo-details open (boolean)
holoDismiss holo-alert, holo-toast None
holoRemove holo-tag None
holoCarouselChange holo-carousel { index, item }
holoAccordionToggle holo-accordion Toggle detail object

Child components also emit internal events that bubble up (holoTabActivate, holoRadioSelect, holoMenuItemSelect, holoListItemSelect, holoAccordionItemToggle). Prefer the parent events above. Each component page lists its own events in the API reference.

Form-associated controls. holo-text-field, holo-textarea, holo-select, holo-checkbox, holo-radio-group, holo-switch and holo-slider are form-associated custom elements. Give them a name and they submit with the surrounding <form>, take part in FormData and report validity. You do not need a hidden input.

The per-component imports are bare module specifiers, so run this script through a bundler (or an import map).

<form id="signup">
<holo-text-field name="email" type="email" label="Email" required></holo-text-field>
<holo-checkbox name="terms" label="I agree" required></holo-checkbox>
<holo-button label="Create account" variant="primary"></holo-button>
</form>
<script type="module">
import '@philbob-sideprojects/hololink-ui/holo-text-field';
import '@philbob-sideprojects/hololink-ui/holo-checkbox';
import '@philbob-sideprojects/hololink-ui/holo-button';
const form = document.querySelector('#signup');
const email = form.querySelector('holo-text-field');
email.addEventListener('holoChange', event => {
console.log('email is now', event.detail);
});
form.addEventListener('submit', event => {
event.preventDefault();
console.log(Object.fromEntries(new FormData(form)));
});
</script>

Tell the Vue compiler that tags starting with holo- are custom elements. Without this, Vue warns that it cannot resolve the component.

vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: tag => tag.startsWith('holo-'),
},
},
}),
],
});

Vue passes :value="email" to the element. For values that must be set as properties, use the .prop modifier (:value.prop="email"). v-model does not work on custom elements; read event.detail and write it back as shown.

React 18 and earlier set unknown props on custom elements as attributes, and they do not subscribe to custom event names. The pattern that works in every React version is a ref, setting properties imperatively and attaching listeners with addEventListener.

SignupForm.jsx
import { useEffect, useRef, useState } from 'react';
import '@philbob-sideprojects/hololink-ui/holo-text-field';
import '@philbob-sideprojects/hololink-ui/holo-button';
export function SignupForm() {
const fieldRef = useRef(null);
const [email, setEmail] = useState('');
useEffect(() => {
const el = fieldRef.current;
const onChange = event => setEmail(event.detail);
el.addEventListener('holoChange', onChange);
return () => el.removeEventListener('holoChange', onChange);
}, []);
useEffect(() => {
fieldRef.current.value = email;
}, [email]);
return (
<form>
<holo-text-field ref={fieldRef} name="email" label="Email" />
<holo-button label="Save" variant="primary" />
</form>
);
}

Use class through className, as usual. Boolean attributes such as disabled are safest set through the ref too.

Allow unknown elements in each component or module that uses holo-* tags with CUSTOM_ELEMENTS_SCHEMA.

signup.component.ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@philbob-sideprojects/hololink-ui/holo-text-field';
import '@philbob-sideprojects/hololink-ui/holo-button';
@Component({
selector: 'app-signup',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<form>
<holo-text-field name="email" label="Email" [value]="email" (holoChange)="onChange($event)"></holo-text-field>
<holo-button label="Save" variant="primary"></holo-button>
</form>
`,
})
export class SignupComponent {
email = '';
onChange(event: Event) {
this.email = (event as CustomEvent<string>).detail;
}
}

With CUSTOM_ELEMENTS_SCHEMA, Angular binds [value] as a DOM property and (holoChange) as a listener for the exact event name. In an NgModule app, add schemas: [CUSTOM_ELEMENTS_SCHEMA] to the module instead.

Astro renders unknown tags as plain HTML, so no configuration is needed. Load the components in a client-side <script>, which Astro bundles with Vite.

src/pages/signup.astro
---
---
<form id="signup">
<holo-text-field name="email" label="Email"></holo-text-field>
<holo-button label="Save" variant="primary"></holo-button>
</form>
<script>
import '@philbob-sideprojects/hololink-ui/styles/global.css';
import '@philbob-sideprojects/hololink-ui/holo-text-field';
import '@philbob-sideprojects/hololink-ui/holo-button';
const field = document.querySelector('holo-text-field');
field?.addEventListener('holoChange', event => {
console.log((event as CustomEvent<string>).detail);
});
</script>

This docs site loads its components the same way, from a shared Astro component.