Komponenten
Auf dieser Seite
sitelo-ui ist eine Komponentenbibliothek für sitelo. Jede Komponente ist eine Funktion, die einen HTML-String zurückgibt, und fügt sich damit direkt in einen javascript-to-html-Baum ein — ohne Compiler, ohne Runtime, ohne Hydration. Was du baust, ist genau das, was in dist/ landet.
Sie kommt mit sitelo, unter dem Einstiegspunkt sitelo/ui.
Schnellstart
npm install sitelo javascript-to-htmlSetze styles() in den Head und rufe die Komponenten im Body auf. Mehr Einrichtung gibt es nicht:
import { body, head, html, meta, title } from 'javascript-to-html'
import { styles, container, stack, heading, text, button } from 'sitelo/ui'
export default () => html({ lang: 'de' },
head(
meta({ charset: 'utf-8' }),
meta({ name: 'viewport', content: 'width=device-width, initial-scale=1' }),
title('Meine Website'),
styles(),
),
body(
container({ size: 'md' },
stack({ gap: 'md' },
heading({ level: 1 }, 'Hallo'),
text({ variant: 'lead' }, 'Eine Seite aus Komponenten.'),
button({ href: '/docs' }, 'Zur Dokumentation'),
),
),
),
)Die Namen der Komponenten entsprechen absichtlich dem, was sie rendern — weshalb einige davon (button, input, table, link, code, select, progress) mit den Element-Funktionen von javascript-to-html kollidieren. Importiere die Bibliothek als Namespace, wenn du beides brauchst:
import * as ui from 'sitelo/ui'
ui.card(
ui.cardHeader({ title: 'Routing', subtitle: 'Dateibasiert' }),
ui.cardBody(ui.text('src/about.ht.js wird zu /about.')),
ui.cardFooter({ divided: true }, ui.button({ size: 'sm' }, 'Mehr erfahren')),
)Die Aufrufkonvention
Jede Komponente nimmt ein optionales Props-Objekt, gefolgt von ihren Kindern — genau wie ein javascript-to-html-Element. Props, die die Komponente kennt, werden nach Namen verbraucht; alles andere landet als Attribut auf dem gerenderten Element, sodass id, data-*, aria-* und Event-Attribute funktionieren, ohne dass die Bibliothek sie aufzählen müsste:
button({ id: 'save', 'data-analytics': 'save-click', onclick: 'save()' }, 'Speichern')
// <button type="button" id="save" data-analytics="save-click" onclick="save()" class="su-btn …">Eine Prop, deren Wert die Komponente nicht kennt — variant: 'nonsense' — fällt auf den Standard zurück, statt zu werfen. Ein kosmetischer Tippfehler sollte keinen Build scheitern lassen.
Styling
styles() gibt ein <style>-Element mit dem gesamten, minifizierten Stylesheet zurück. Das sind rund 7 kB über die Leitung, und es kann nicht aus dist/ verschwinden — deshalb ist es der Standard. Wenn du es lieber einmal verlinkst und der Browser es seitenübergreifend cachen soll, importiere das CSS stattdessen aus einer gebündelten Einstiegsdatei; Vite gibt es dann aus:
// src/main.js — von Vite gebündelt, seitenübergreifend gecacht
import 'sitelo/ui/styles.css'Nimm das eine oder das andere, nicht beides.
Theming
Alles läuft über CSS-Custom-Properties auf :root — fünf Paletten, eine Abstandsskala, Radien, Typografie und Schatten. theme() schreibt Überschreibungen dafür und nimmt camelCase-Namen (radiusMd → --su-radius-md), Paletten-Objekte oder wörtliche Custom Properties:
import { styles, theme } from 'sitelo/ui'
head(
styles(),
// Nach styles(), damit diese Werte gewinnen.
theme({
primary: { base: '#5b5bd6', hover: '#4a4ac4', fg: '#ffffff' },
radiusMd: '2px',
fontSans: '"Inter", system-ui, sans-serif',
}, {
dark: { primary: { base: '#8f8ff0' } },
}),
)Der Dunkelmodus ergibt sich von selbst aus prefers-color-scheme. Setzt du data-theme oder data-su-theme auf einem beliebigen Vorfahren auf light oder dark, gewinnt das — und genau das tut themeToggle():
import { styles, themeScript, themeToggle } from 'sitelo/ui'
head(
themeScript(), // wendet die gespeicherte Wahl vor dem ersten Rendern an
styles(),
)
// …irgendwo im body
themeToggle()JavaScript, und wie wenig davon nötig ist
Die meisten Komponenten brauchen keines. Modal und Drawer sind popover-Elemente, also übernimmt der Browser das Öffnen, den Hintergrund, den Klick nach außen und Escape. Das Akkordeon ist ein <details name>. Menüs sind <details>. Tooltips sind CSS.
Vier Dinge wollen tatsächlich ein Skript — und jedes holt es sich selbst:
- Tabs, deren Panels an Ort und Stelle wechseln
- der Schließen-Button einer ausblendbaren Meldung
- das Schließen eines Menüs per Klick nach außen oder Escape
- der Theme-Umschalter
In der Einstiegsdatei ist dafür nichts zu ergänzen — der Import steht im Event-Attribut:
<!-- rendered by alert({ dismissible: true }) -->
<button class="su-alert-dismiss"
onclick="import('/su/alert.js').then(m=>m.dismiss(this))">
×
</button>sitelo liefert diese Module während der Entwicklung unter /su/ aus und kopiert beim Build genau die, auf die deine Seiten tatsächlich verweisen. Jedes ist deutlich kleiner als ein Kilobyte, keines wird vor der ersten Interaktion geladen, und bis dahin rendert jede Komponente korrekt: Panel-Tabs zeigen das Panel, das der Server als aktiv markiert hat, Menüs öffnen und schließen von allein, und der Schließen-Button tut schlicht nichts.
Die Ausnahme ist toast(), denn dafür gibt es auf der Seite keinen Auslöser:
// src/main.js
import { toast } from 'sitelo/ui/client'Beispiele
Formulare verdrahten ihre Labels, IDs, Hilfetexte und Fehlermeldungen selbst:
import { card, cardBody, cardFooter, button, stack, textField, selectField } from 'sitelo/ui'
card(
cardBody(
stack({ gap: 'md' },
textField({ label: 'E-Mail', name: 'email', type: 'email', help: 'Wird nie weitergegeben.' }),
textField({ label: 'Website', name: 'site', startAdornment: 'https://', error: 'Das ist keine URL.' }),
selectField({ label: 'Tarif', name: 'plan', options: ['Kostenlos', 'Pro'], value: 'Pro' }),
),
),
cardFooter({ divided: true }, button({ type: 'submit' }, 'Speichern')),
)Ein Modal ist ein Popover, und sein Auslöser ist jeder Button, der auf seine ID zeigt:
import { button, modal } from 'sitelo/ui'
button({ popovertarget: 'confirm' }, 'Löschen…')
modal({
id: 'confirm',
title: 'Diese Seite löschen?',
footer: button({ color: 'danger' }, 'Löschen'),
}, 'Das lässt sich nicht rückgängig machen.')Tabs gibt es in zwei Formen — als Links oder mit Panels:
// Link-Tabs: eine Seite pro Tab, ganz ohne Skript.
tabs({ items: [
{ label: 'Docs', href: '/docs', active: true },
{ label: 'API', href: '/api' },
] })
// Panel-Tabs: wechseln an Ort und Stelle und holen sich den Code dafür selbst.
tabs({ value: 'use', items: [
{ id: 'install', label: 'Installieren', panel: code('npm install sitelo') },
{ id: 'use', label: 'Verwenden', panel: code("import * as ui from 'sitelo/ui'") },
] })Tabellen nehmen columns und rows, mit einer render-Funktion überall dort, wo eine Zelle mehr als einen Wert braucht:
table({
striped: true,
columns: [
{ key: 'page', header: 'Seite' },
{ key: 'size', header: 'Größe', align: 'end' },
{ header: 'Status', render: (row) => chip({ color: row.ok ? 'success' : 'danger' }, row.ok ? 'ok' : 'fehlgeschlagen') },
],
rows: pages,
})Das Verzeichnis examples/ui im Repository rendert jede Komponente auf einer einzigen Seite — der schnellste Weg, den ganzen Satz zu sehen.
Komponenten-Referenz
Alle Exporte, nach Gruppe. Die Props sind typisiert: sitelo/ui liefert .d.ts-Dateien mit, sodass ein Editor variant, color und size in JavaScript ebenso vervollständigt wie in TypeScript.
| Gruppe | Komponenten |
|---|---|
| Layout | container, stack, grid, divider, card, cardHeader, cardTitle, cardSubtitle, cardMedia, cardBody, cardFooter, aspectRatio |
| Typografie | text, heading, link, code, inlineCode, kbd, visuallyHidden, prose |
| Eingaben | button, iconButton, buttonGroup, field, input, textarea, select, textField, textareaField, selectField, checkbox, radio, toggle, choiceGroup, slider, sliderField, toggleButton, toggleGroup |
| Datenanzeige | avatar, avatarGroup, badge, chip, tooltip, table, list, listItem, figure |
| Rückmeldung | alert, progress, spinner, skeleton, toasts, empty |
| Navigation | breadcrumbs, pagination, tabs, appBar, appBarNav, appBarSpacer, appBarActions, navLink, themeToggle |
| Overlays | modal, drawer, closeButton, menu, menuItem, menuSeparator, accordion, accordionItem, collapsible |
| Abschnitte | hero, footer, siteFooter, footerColumn, footerBottom, stat, statGroup, steps, timeline, timelineItem, mockup |
| Styling | styles, stylesheet, theme, themeScript |
Zwei Namen weichen von dem ab, was man erwarten würde: der Schalter heißt toggle, weil switch ein reserviertes Wort ist und keine Import-Bindung sein kann; und der gestylte Anker wird sowohl als link als auch als textLink exportiert, damit er neben dem link von javascript-to-html stehen kann. table, input, select und progress haben denselben Notausgang: dataTable, textInput, selectField, progressBar.