Kern 0.12.0 GitHub
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

Cloud connection
SaveReset
<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; }