Guide
Getting started
Install the package, load the stylesheet once and register the elements. Everything else is HTML.
Install
npm install @yfridelance/kern
Without a framework
Load kern.css (tokens, theme and base styles) once per page and the loader module. Elements load themselves on first use (lazy loading per component). The inline script sets the theme before the first paint, so there is no flash.
<script>try{document.documentElement.dataset.theme=localStorage.getItem('kern-theme')==='light'?'light':'dark'}catch(e){document.documentElement.dataset.theme='dark'}</script>
<link rel="stylesheet" href="node_modules/@yfridelance/kern/dist/kern/kern.css">
<script type="module" src="node_modules/@yfridelance/kern/dist/kern/kern.esm.js"></script>
From a CDN without a build step:
<link rel="stylesheet" href="https://unpkg.com/@yfridelance/kern/dist/kern/kern.css">
<script type="module" src="https://unpkg.com/@yfridelance/kern/dist/kern/kern.esm.js"></script>
With a bundler
import { defineCustomElements } from '@yfridelance/kern/loader';
import { initTheme } from '@yfridelance/kern';
initTheme(); // saved theme, otherwise dark
defineCustomElements(); // registers all kern-* elements
Add @yfridelance/kern/kern.css to your global styles. Helpers such as confirm(), toast(), parseIpv4() and parseMac() are imported from @yfridelance/kern.
Angular
The simplest way works with every Angular version: register the elements once and allow custom elements in your components. Bind properties and listen to the kern… events.
// main.ts
import { defineCustomElements } from '@yfridelance/kern/loader';
import { initTheme } from '@yfridelance/kern';
initTheme();
defineCustomElements();
// device-form.component.ts
@Component({
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<kern-input label="Name" [value]="name" (kernChange)="name = $event.detail.value"></kern-input>
<kern-select label="Site" [options]="sites" [value]="site" (kernChange)="site = $event.detail.value"></kern-select>
`,
})
export class DeviceFormComponent { … }
For ngModel and reactive forms the package also ships generated standalone wrappers with ControlValueAccessors as source in angular/src/directives/ (proxies.ts, text-value-accessor.ts, number-value-accessor.ts, boolean-value-accessor.ts). They are not a package entry point; point a TypeScript path alias at the folder and import from there:
// tsconfig.json
{ "compilerOptions": { "paths": { "@kern/angular/*": ["node_modules/@yfridelance/kern/angular/src/directives/*"] } } }
import { KernInput } from '@kern/angular/proxies';
import { TextValueAccessor } from '@kern/angular/text-value-accessor';
@Component({
standalone: true,
imports: [ReactiveFormsModule, KernInput, TextValueAccessor],
template: `<kern-input label="Name" [formControl]="name"></kern-input>`,
})
export class DeviceNameComponent { name = new FormControl(''); }
Router links
kern-button, kern-link and kern-list-item render their <a href> in the shadow DOM, so routerLink cannot catch the click and the browser would reload the page. A plain left click emits the cancelable kernNavigate with { href, event } first: call preventDefault() and let the router navigate. Ctrl/Cmd click, middle click, target="_blank" and download emit nothing, so "open in new tab" keeps working.
@Component({
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<kern-button href="/templates/new" variant="primary"
(kernNavigate)="$event.preventDefault(); router.navigateByUrl($event.detail.href)">New template</kern-button>
<kern-link href="/devices/7" (kernNavigate)="$event.preventDefault(); router.navigateByUrl($event.detail.href)">Bad Lüftung</kern-link>
`,
})
export class TemplatesComponent { router = inject(Router); }
Angular collapses whitespace in templates. Bind [code] on kern-code (or use ngPreserveWhitespaces) when you show multi-line code.
A first page
<form>
<kern-input name="name" label="Device name" required></kern-input>
<kern-ip-input name="ip" label="IP address" allow="single"></kern-ip-input>
<kern-switch name="cloud" checked>Cloud connection</kern-switch>
<kern-button type="submit" variant="primary">Save</kern-button>
</form>
Styling from outside
Components use Shadow DOM. Change their look only through the tokens (CSS custom properties, see Colour tokens) and the documented ::part() names listed on each component page. Do not invent colours in apps.
kern-select::part(listbox) { max-height: 240px; }