@node-k/ui (0.2.0)
Installation
@node-k:registry=npm config set @node-k:registry=npm install @node-k/ui@0.2.0"@node-k/ui": "0.2.0"About this package
@node-k/ui — Bausteine als Web Components
Stufe 4 aus
korrekturen/paketierung.md§7. Angelegt 11.9.2026 mitnk-buttonals Muster (4a). Entscheidungen 11.9.: form-associated ja · Lit vendored · Workspaceui/·.jsx-Wrapper später als eigenes Paket · diese Sitzung nur Button.
Benutzen
<script type="module" src="node_modules/@node-k/ui/src/define/nk-button.js"></script>
<link rel="stylesheet" href="node_modules/@node-k/tokens/dist/css/nodek-tokens.css">
<nk-button>Node finden</nk-button>
<nk-button variant="secondary">Mitglied werden</nk-button>
<nk-button variant="ghost" size="sm">Mehr</nk-button>
<nk-button full-width type="submit">Anmelden</nk-button>
Tokens greifen als Custom Properties durch das Shadow DOM — die Komponente bringt keine Farben mit.
Ohne nodek-tokens.css ist sie unsichtbar; das ist Absicht (eine Quelle, kein Rückfall-Zweitsatz).
| Attribut | Werte | Standard |
|---|---|---|
variant |
primary · secondary · ghost |
primary |
size |
sm · md · lg |
md |
type |
button · submit · reset |
button |
disabled |
boolean | — |
full-width |
boolean | — |
label |
aria-label für Icon-Buttons ohne Text |
— |
--nk-button-surface |
Grundfläche hinter Umriss-Varianten | var(--nk-black) |
part="button" und part="label" für ::part()-Styling von außen.
Bauart — das Muster für 4b
Drei Dinge einmal richtig, in src/shared/frame.js, von jeder Komponente importiert:
- Doppelebene.
.frame= äußere gefaste Fläche in Linienfarbe,padding: var(--nk-frame-line-<klasse>),border: 0..fill= innere Fläche mit--nk-frame-clip-<klasse>-<richtung>-in. Richtung steht im Token-Namen:aktivfür alles Fokussierbare (Button, Input, Switch),passivfür den Rest. :focus-visibleim Shadow DOM. Aufbau<span class="ring"><button class="frame">…</button></span>. Der Ring liegt auf dem ungeclippten Wrapper.ring:has(:focus-visible)(outline: var(--nk-focus),outline-offset: var(--nk-focus-offset)) — außen, auf der Bühne, von der Knopffarbe durch den Offset getrennt. Der Host hatdelegatesFocus: true; keintabindexam Host. Zwischenkorrektur 2 (12.9., F4)::host(:focus-visible)trifft bei delegatesFocus in Chromium nie (der Host trägt nur:focus-within),:host(:has(…))sieht nicht in den eigenen Shadow Root,:host(:focus-within)zeichnet auch bei Maus. Nur der Wrapper im selben Baum erfüllt alle drei: Tab zeichnet, Maus nicht, nichts wird weggeschnitten.check.mjsweist:host(:focus-visible)seit 12.9. ab. Entscheidung Zwischenkorrektur 11.9.: das frühere Innenband (Grund + Ring) maß auf dem Primärknopf Türkis auf Orange 1,25:1. Ein Ring innen bräuchte je Fläche einen zweiten Wert; außen reicht ein Token. Gemessen am Primärknopf: Ring--nk-focus-ring#00baba gegen Bühne--nk-black#171717 = 7,45:1 — auf beiden Seiten des Rings, weil der Offset ihn von der orangen Kante trennt. Hell: #007070 auf--nk-paper#f5f5f5 = 5,36:1. Kacheln (tileLink) behielten bis G1 das Innenband (Grund--nk-surface#1c1c1c → 7,46:1; hell auf--nk-surface#ffffff → 5,80:1) — gerechnet, nicht gezeichnet: das::afterlag bis auf seine äußerste Zeile hinter.fill, gezeichnet wurde 1 px Ring, hell 3,07:1 gegen die Linie. R9 (12.9., umgesetzt G1.2 am 22.9.): der Ring der Kacheln liegt außen auf<span class="ring">um die Kachel,.ring:has(.title-link:focus-visible)— dieselbe Bauart wie beim Knopf, ein Token auf der Bühne (dunkel 7,46:1, hell 5,29:1), Platz aus--nk-frame-gap8 = 2 + 2 + 4. F5 (12.9.): Kachelschmuck (Linie, Lift) reagiert auf den Fokus nur noch über.frame:has(.title-link:focus-visible), nicht über:focus-within— das traf auch geslottete Knöpfe, und Titel-Halt und Knopf-Halt gaben dasselbe Bild. Regel: der Indikator markiert das Bedienelement, das den Fokus hat, nicht seine Umgebung.nk-cardhat kein Tastatur-Pendant zuhovermehr; es gibt in der Karte nichts eigenes zu fokussieren.prefers-reduced-motion.transitionundtransformaus Tokens, unterreducebeidenone.
Dazu: static formAssociated = true + attachInternals() in jeder Komponente mit Formularbezug.
Beim Button liefert das this.form ohne closest() und type="submit" löst form.requestSubmit() aus.
nk-input und nk-switch melden ihren Wert über ElementInternals.setFormValue() — dann greift
die Angular-Direktive nur noch mit [field] an (paketierung.md §4.2a).
Hover nur unter @media (hover: hover). Farbe nur var(--nk-*) und color-mix(…) — build/check.mjs
prüft das, zusammen mit den drei P5-Bedingungen, dem Lit-Importpfad und seit P1 (22.9.) der Form: border-radius nur 0
(--nk-r-pill nur in nk-status-dot und nk-logo), kein border ≠ 0 neben background, kein clip-path neben border ≠ 0,
keine nackte Fasengröße in polygon(). Der Zwilling in styles/build/check.mjs zählt dieselben Regeln im Baum gegen eine
Sperrklinke (FORM_CAP). Die Prüfung folgt der Importkette: wer
focusInset/motion aus shared/frame.js importiert, hat die Bedingung erfüllt; Kommentare werden vorher entfernt.
Ein Selbsttest läuft bei jedem check mit (Probe A ohne Import muss anschlagen, Probe B mit Import nicht).
4b · Kacheln mit Titel-Link (entschieden 11.9.)
Frage aus dem Auftrag: ganze Kachel klickbar oder nur Titel und Aktion? Entschieden: der Titel ist der Link,
die Fläche erweitert nur den Klickbereich. Umgesetzt in shared/frame.js als tileLink:
- Die Kachel ist passiv — nicht fokussierbar, Fase oben links + unten rechts (
frame('card','passiv')). - Der Titel ist das einzige interaktive Element:
<a href>wennhrefgesetzt (Nuxt, SSR, Router), sonst<button>mit Eventnk-select(bubbles,composed,detail) wennselectablegesetzt (Angular). Ohne beides ist der Titel reiner Text — nichts zu fokussieren, nichts zu versprechen. .title-link::after { position:absolute; inset:0 }dehnt den Klickbereich auf die Kachel. Der Fokusring liegt seit G1.2 außen auf dem ungeclippten.ringum die Kachel (.ring:has(.title-link:focus-visible)), die Unterstreichung und der Screenreader-Name bleiben beim Titel.- Geslottete Tags und Aktionen (
::slotted(*),.raised) liegen mitz-index:1über dem::afterund bleiben eigenständig klickbar — der PostCard-Fall (echter Button in der Kachel) funktioniert damit ohne Sonderregel. - Hover (nur
hover: hover) und:focus-withinfärben die Rahmenlinie in der Akzentfarbe; damit hat die Tastatur dasselbe Signal wie der Zeiger (P5). - Status in
nk-node-cardist Text + Punkt, nie Farbe allein; hell trägt er--nk-link/--nk-orange-text(P9.6/9.7).
| Komponente | Attribute | Slots | Event |
|---|---|---|---|
nk-event-row |
day month title datetime href target selectable |
default = Tags/Meta · action |
nk-select |
nk-node-card |
name description status status-label href target selectable |
action |
nk-select |
nk-card |
hover hover-color (turq·orange) accent size (card·panel) |
default | — |
nk-input |
value name type label placeholder prompt disabled required readonly autocomplete |
— | input change |
nk-switch |
checked name value label disabled |
default (Beschriftung) | change |
nk-badge |
tone (orange·turq·ok·danger·neutral) solid |
default | — |
nk-tag |
variant (topic·meta) |
default | — |
nk-mono-label |
color (turq·orange·dim·faint) no-prompt cursor |
default | — |
nk-status-dot |
status (open·event·closed) label |
default (eigener Text) | — |
nk-aktions-panel |
claim qr-label no-qr |
default (Kontaktzeilen) · qr |
— |
nk-post-card |
image image-label aspect tag tag-color (turq·orange) date title excerpt cta-label href |
image · action |
nk-action |
nk-logo |
fassung (marke·zeichen·wortmarke·signet) farbig signet (orange-ink·ink-orange·tuerkis-ink·ink-tuerkis) mono size base label |
— | — |
K3 §3 (12.9.) — nk-logo, nacktes HTMLElement. Entscheidungen aus dem Formular: ein Attribut fassung; mode
gestrichen; nur rund; SVG statt PNG. Umsetzung: das Element lädt die tokenisierte Quelle aus assets/svg/ und
bettet sie inline in den Shadow Root — inline darf SVG var(--nk-*) tragen (CLAUDE.md), also folgen .nk-t/.nk-b/
.nk-a dem Modus des Kontexts ohne Attribut, auch verschachtelt; der helle Pol entsteht aus denselben Tokens statt
aus einer zweiten Datei. Kein Zuschnitt-Kniff (die SVGs sind randlos), kein filter: mono setzt die Tokenklassen
auf currentColor. Signet = app/k-*.svg (modusfest), rund über --nk-r-pill auf dem Quadrat. base zeigt auf
das Verzeichnis mit assets/ (Standard: Designsystem-Wurzel; Konsumenten setzen es auf ihren @node-k/assets-Pfad).
Offener Punkt: ein eigenständiges Emblem (Netz + K) existiert in assets/svg nicht, nur als PNG. zeichen ist
deshalb die Wort-Bild-Marke ohne Wortmarke (.nk-t ausgeblendet) — die Wortmarke liegt im Netz, ein Ausschnitt
schnitte Text mit. Soll das Emblem als Quelle nachgezeichnet werden, ist das ein eigener Auftrag. Probe:
ui/probe/nk-logo.html (Fassungen, Größen, mono, feste Pole, beide Modi).
K3 (12.9.) — nk-aktions-panel, nk-post-card; nk-logo folgt nach Klärung der Fassungen.
nk-aktions-panel: Klasse panel, passiv, Linie --nk-ink. Der Fehler aus K3 §1 ist behoben: Grund --nk-orange ist
modusfest, Schrift deshalb --nk-ink (nicht --nk-black) — #171717 auf #fbaa2b = 9,29:1 in beiden Modi; mit
--nk-black wäre hell Papier #f5f2ec auf Orange = 1,73:1. Punktraster aus Ink 15 %, Raster --nk-space-6, Punkt
--nk-frame-line-xs. QR-Fläche 56 (48 + 8) in --nk-white — Modulmatrix, der eine legitime Ort. check.mjs prüft seit
12.9. zusätzlich: modusfester Grund (--nk-orange|turq|ink|paper) + modusfolgende Schrift (--nk-black|text*|surface)
im selben Block ist ein Befund (Gegenprobe: Panel mit --nk-black schlägt an).
nk-post-card: Klasse card, passiv; Vorbild bleibt — der geslottete nk-button (Fallback „Weiterlesen →" →
nk-action, oder href) ist das einzige fokussierbare Element, kein tileLink, kein :focus-within. Hover färbt
Linie in Tag-Farbe (--nk-link / --nk-orange-text) und hebt um --nk-lift-sm, unter reduced-motion aus. Tag als
xs-Rahmen direkt aus den Clip-Tokens (eigene Klassennamen). Elf nackte Zahlen → --nk-fs-micro|sm|h4, --nk-lh-snug|normal,
--nk-space-*. Probe: ui/probe/nk-zusammengesetzte-k3.html — Tab hält nur auf den drei Knöpfen der Karten.
K2 (12.9.) — die vier Kleinteile, keines fokussierbar. nk-badge und nk-tag: Lit + frame('chip','passiv'), Fase
oben links + unten rechts, Linie 1,5px aus --nk-frame-line-chip; kein focusInset, kein motion (kein Übergang);
nk-check: nicht-fokussierbar ohne-bewegung im Kopf. Badge-Schrift: Tönung aus dem Text-Token des Tons
(--nk-orange-text, hell #8f5a00 auf #f7e2b8 = 5,9:1 — --nk-orange läge bei 1,5:1); gefüllt Orange → --nk-ink (9,3:1,
modusfest, §6), gefüllt turq/ok/danger/neutral → --nk-black (modusfolgend: dunkel Ink auf hellen Signalfarben,
hell Paper auf den dunklen Rollenfarben #007070/#186f50/#bf1400/#565656 = 5,0–6,6:1; Ink läge dort bei 2,8–3,0:1). nk-mono-label und nk-status-dot sind nackte HTMLElements
ohne Lit — gleiche Entscheidung wie nk-netz/nk-qr: keine Fläche, kein Zustand, kein Bedienelement, alle Attribute
wirken über :host([…])-CSS; das einzige JS ist der Standardtext je Status im Punkt. Lit brächte nur Gewicht. Der
Cursor des Labels blinkt über @keyframes im Shadow Root und steht unter prefers-reduced-motion still. --nk-r-pill
nur auf dem quadratischen Punkt. Größen: Punkt über --nk-status-dot-size,
Label über geerbte font-size. Probe: ui/probe/nk-kleinteile.html — Tab hält nur am Modusschalter und am nativen
Knopf am Ende.
K1 (12.9.) — nk-input, nk-switch, beide form-associated. static formAssociated = true, attachInternals(),
setFormValue() in updated(); formDisabledCallback (auch <fieldset disabled>), formResetCallback,
formStateRestoreCallback. nk-input: Klasse btn, aktiv; Pflichtfeld über setValidity({valueMissing}), Ungültigkeit
als invalid-Attribut gespiegelt, erst nach change (nicht beim Tippen). Linie bei Fokus Türkis über
.frame:has(input:focus-visible) — am eigenen Feld, keine Umgebung. nk-switch: natives <button role="switch" aria-checked> — Leertaste und Enter schalten ohne eigenen Tastatur-Code; Text im <label> um den Button, Klick auf den
Text schaltet. Formularwert wie Checkbox: value (Standard on) nur wenn checked. Seit G1.1 (22.9.) gefast statt rund:
Doppelebene Klasse xs, aktiv — Spur 40×24 (Fase 4, Linie 1), innen 38×22 (Innenfase 3,41), Knopf 16 mit Formstufe xxs
(Fase 3 — am Bild entschieden, 2 px las sich bei 1 dpr als Rundung), Weg 16, alles aus --nk-space-* und --nk-frame-*; aus = umrissen (--nk-border-strong/--nk-surface), ein =
gefüllt --nk-accent-turq, Knopf dort --nk-ink (P1 §7: 7,46:1 modusfest; --nk-text maß dunkel 2,15:1). Probe: ui/probe/nk-formular.html (FormData, reset, fieldset disabled), ui/probe/g1-geometrie.html (Maße, Fasen, Kontraste).
Tastatur-Ablauf nk-formular.html: Tab → Name (Ring + Linie Türkis), Tippen → input, Tab weg → change, leer +
required → invalid (Linie --nk-danger) · … → Newsletter-Schalter (Ring um die Spur), Leertaste → aus, Enter → an,
change am Host · Deaktivierte werden übersprungen · Absenden → FormData {name, mail, cmd, ro, news} (pub fehlt, weil aus)
· Zurücksetzen → Startwerte, Schalter checked wie im Markup.
nk-card (Basis für PostCard, 11.9.): passiver Behälter, nicht fokussierbar; Akzent als Innenkante der Füllfläche statt
als dickerer Rand, damit Fase und Kante nicht kollidieren. :focus-within im Slot gibt dasselbe Signal wie Hover.
--nk-card-surface übersteuert den Grund. Probe: ui/probe/nk-card.html.
Offen in 4b: Logo, PostCard, AktionsPanel; nk-tag und nk-status-dot aus 4a, damit die Kacheln sie
statt Inline-Markup slotten.
4c · Die zwei Sonderfälle: nk-netz, nk-qr (entschieden 11.9.)
Frage aus dem Auftrag: brauchen die beiden Lit oder genügt ein nacktes Custom Element? Nacktes HTMLElement.
Beide haben kein Template zu diffen, nichts zu fokussieren, keine Doppelebene — Lit brächte nur Gewicht. Die
Kerne bleiben Quelle in components/netz/net-device.js und components/qr/nodek-qr.js; npm run vendor:core -w ui
kopiert sie nach vendor/core/ (check schlägt an, wenn Kopie und Quelle abweichen). src/** importiert nur
../../vendor/core/…. Im Arbeitsbereich liegt für nodek-qr.js nur ein Durchreich-Platzhalter (export * from ../../../components/qr/nodek-qr.js), weil der Design-System-Compiler sonst doppelte Exporte meldet; vendor:core
ersetzt ihn durch die echte Kopie. net-device.js (UMD, keine Exporte) liegt direkt als Kopie.
Befund beim Prüfen: components/netz/NetCanvas.jsx war keine Hülle um den Kern — es rechnete ein eigenes
Mini-Netz mit rgba()-Literalen und ohne Seed. nk-netz wrappt den echten Kern (makeNet/drawNet): seeded,
gebündelte Pfade, optionale Form (el.shape = { points, fit:'contain' } für Kärnten). nodek-qr.js bringt mit
defineGenerator() schon ein Custom Element mit — das backt den Stil aber zur Definitionszeit ein und kennt keine
Tokens. nk-qr liest die Farben je Render aus den Custom Properties des Hosts.
- Farben ausschließlich aus
--nk-orange,--nk-turq,--nk-blackam Host (getComputedStyle(this)). Fehlen die Tokens, greift beim Netz der Fallback des Kerns (dort begründet), beim QR der Renderer-Default. Insrc/**steht kein Literal. nk-netz: Bewegung nur wenn sichtbar (IntersectionObserver), nichtstaticundprefers-reduced-motionnicht gesetzt — sonst genau ein Standbild. Das ist die Regel für alle bewegten Bausteine. Hostaria-hidden="true",part="canvas", DPR bis 2. Moduswechsel:el.refresh().nk-qr: Shadow-divmitrole="img"undaria-label=title, sonst Inhalt (bei strukturierten Typen „QR-Code", damit kein WLAN-Passwort vorgelesen wird).part="qr". Rendert neu bei Attributänderung,refresh()und Wechsel vonprefers-color-scheme;resultliefert{ svg, geometry, moduleCount, version, ecc }.check.mjs: die P5-Pflichten gelten weiter, Ausnahmen erklärt die Komponente im Kopf —nk-check: nicht-fokussierbar(kein:focus-visiblenötig) undnk-check: ohne-bewegung(keinprefers-reduced-motionnötig). Nur mit Begründung.
| Komponente | Attribute | Eigenschaften |
|---|---|---|
nk-netz |
density seed speed link-dist opacity glow static |
shape (setzen) · sim · refresh() |
nk-qr |
content type fields (JSON) ecc size frame print logo logo-scale title transparent |
result · refresh() |
Probe: ui/probe/sonderfaelle.html — Netz hinter Inhalt, static, glow="0", drei QR-Fassungen, Modusschalter.
Lit: vendored, nicht transitiv
vendor/lit/lit.js ist ein einzelnes ESM-Bündel (LitElement, html, css, svg, nothing, unsafeCSS,
classMap, ifDefined), erzeugt von npm run vendor -w ui aus der devDependency lit@3.3.1. Konsumenten
bekommen genau diese Fassung, ohne eigene Lit-Auflösung — eine Version in allen drei Apps. src/**
importiert ausschließlich ../../vendor/lit/lit.js; jeder andere Lit-Import fällt im check.
Stand 11.9.: die Registry ist aus dem Designsystem heraus nicht erreichbar. vendor/lit/lit.js
reicht deshalb den Shim lit.shim.js durch — eine Minimal-Oberfläche derselben API ohne Diffing,
für Browserproben ausreichend, für Produktion nicht. Der Shim exportiert dieselben Namen wie das echte Bündel
(LitElement, nicht mehr litElementBase), damit die Komponenten unverändert gegen beides laufen. Regel seit der
Zwischenkorrektur: kein „fertig" ohne echtes Lit — Abnahme nur nach:
npm ci
npm run vendor -w ui # esbuild ist devDependency; ersetzt lit.js durch das echte Bündel, löscht den Shim, kopiert die Kerne
npm run check -w ui # Exit 0, keine Shim-/Platzhalter-Warnung mehr
Nachweis im Browser: Probe öffnen, customElements.get('nk-button') liefert die Klasse. Jede Komponente exportiert
ihre Klasse als nk<Name>Element (klein), weil der Design-System-Compiler Großbuchstaben-Exporte hebt (P1.3).
Probe
ui/probe/nk-button.html — alle Varianten und Größen, Modusschalter (:root.light), Fokus- und Formularprobe.
ui/probe/zusammengesetzte.html — nk-event-row, nk-node-card: Link-, Event- und passive Fassung, Slots, Fokus am Titel.
ui/probe/sonderfaelle.html — nk-netz, nk-qr.
Ohne Bundler: ../src/define/nk-button.js als <script type="module">, Tokens aus ../../tokens/dist/css/.
Abnahme 4a
npm ci && npm run verify→ui-check: … keine Verstößenpm run vendor -w ui→vendor/lit/VERSION= 3.3.1, Probe rendert unverändert- Tab erreicht den Button, Ring außen sichtbar (≥ 3:1 gegen Bühne); Enter/Space lösen
clickam Host aus customElements.get('nk-button')gegen echtes Lit (nicht Shim) liefert die Klassetype="submit"in<form>→submit-Event des Formularsprefers-reduced-motion: reduce→ kein Lift, keine Transition- Kontrast Beschriftung: primär Ink auf Orange 9,29:1 · sekundär
--nk-linkauf Grund (dunkel = Türkis 7,46:1 · hell #007070 auf Papier), nicht--nk-turq— P9.7
Dependencies
Dependencies
| ID | Version |
|---|---|
| @node-k/assets | 0.2.0 |
| @node-k/tokens | 0.2.0 |
Development dependencies
| ID | Version |
|---|---|
| esbuild | 0.25.9 |
| lit | 3.3.1 |