7.3 KiB
Styling und CSS-Integration
Keluna verwendet die System-WebView als Renderer. Deshalb sollte CSS die primäre Styling-Technologie sein. Der Kotlin-Code beschreibt Komponenten, Zustand und Verhalten; CSS beschreibt deren visuelle Darstellung.
Dabei soll CSS nicht als getrennte, globale „Web-Schicht“ behandelt werden. Komponenten sollen nach Fachlichkeit bzw. Feature gruppiert werden können, sodass Kotlin-Code und zugehöriges Styling direkt nebeneinander liegen.
Grundprinzip
Eine Keluna-Komponente könnte beispielsweise so organisiert sein:
projectbrowser/
├── ProjectBrowser.kt
├── ProjectBrowser.css
├── ProjectEntry.kt
└── ProjectEntry.css
Damit wird nicht nach Technologien wie kotlin/, css/ und javascript/ getrennt, sondern nach Komponenten bzw. Features.
CSS-Klassen als Styling-Schnittstelle
Komponenten sollten ihren relevanten DOM-Elementen semantische CSS-Klassen geben können.
component("project-entry") {
div(classes = "project-entry") {
span(classes = "project-entry__name") {
text(project.name)
}
span(classes = "project-entry__status") {
text(project.status)
}
}
}
Dazu liegt direkt daneben beispielsweise:
.project-entry {
display: flex;
align-items: center;
gap: 0.5rem;
}
.project-entry__name {
font-weight: 600;
}
.project-entry__status {
opacity: 0.7;
}
Die CSS-Klasse ist damit Teil der Styling-Schnittstelle einer Komponente.
IDs nicht als primärer Styling-Mechanismus
Keluna benötigt intern eindeutige IDs, um DOM-Knoten mit Komponenten im Kotlin-Component-Tree zu verbinden.
Diese IDs sollten jedoch keine öffentliche Styling-Schnittstelle sein.
Intern könnte Keluna beispielsweise erzeugen:
<button data-keluna-id="k184">Open</button>
Diese ID wird automatisch generiert und ausschließlich für Runtime, Events und Synchronisation verwendet.
CSS sollte sich deshalb nicht darauf beziehen.
Falls der Entwickler selbst eine DOM-ID benötigt, etwa für Accessibility, Tests oder die Integration einer externen Web-Komponente, sollte Keluna auch explizite IDs erlauben. Deren Eindeutigkeit liegt dann jedoch in der Verantwortung der Anwendung.
Komponentenlokales CSS
Keluna sollte CSS-Dateien direkt einer Komponente zuordnen können.
Konzeptionell:
class ProjectEntry : Component() {
override val stylesheet = stylesheet("ProjectEntry.css")
}
oder deklarativer:
component("project-entry") {
stylesheet("ProjectEntry.css")
// ...
}
Das Keluna-Gradle-Plugin sammelt diese Stylesheets beim Build und bettet sie zusammen mit den übrigen Web-Ressourcen in die Anwendung ein.
Der Entwickler muss dafür weder einen Webserver noch einen JavaScript-Bundler konfigurieren.
CSS-Isolation
Lokales CSS sollte möglichst nicht versehentlich andere Komponenten beeinflussen.
Keluna könnte deshalb jedem Komponententyp automatisch einen Scope geben:
<div data-keluna-scope="project-entry">
...
</div>
Das Framework kann das lokale CSS beim Build entsprechend scopen.
Aus:
.title {
font-weight: bold;
}
wird intern beispielsweise:
[data-keluna-scope="project-entry"] .title {
font-weight: bold;
}
Damit kann eine Komponente einfache Klassennamen verwenden, ohne globale Namenskollisionen zu verursachen.
Die konkrete Implementierung des Scopings ist noch eine Designentscheidung. Denkbar sind:
- Selector-Rewriting beim Build
- modernes CSS Scoping
- Shadow DOM für dafür geeignete Komponenten
Für normale Keluna-Komponenten erscheint Light DOM mit automatisch gescoptem CSS zunächst attraktiver als Shadow DOM, da globales Theming und die Integration mit der restlichen Anwendung einfacher bleiben.
Globales Styling und Themes
Neben komponentenlokalem CSS braucht eine Anwendung globale Designregeln.
Beispielsweise:
src/
├── App.kt
├── theme.css
└── projectbrowser/
├── ProjectBrowser.kt
└── ProjectBrowser.css
theme.css könnte globale Design-Tokens über CSS Custom Properties definieren:
:root {
--keluna-font-family: system-ui, sans-serif;
--keluna-font-size: 14px;
--keluna-spacing: 0.5rem;
--keluna-border-radius: 4px;
}
Eine Komponente verwendet diese Variablen:
.project-entry {
gap: var(--keluna-spacing);
border-radius: var(--keluna-border-radius);
}
Keluna sollte nach Möglichkeit auch Systempräferenzen wie Dark Mode über normale CSS-Möglichkeiten nutzbar machen:
@media (prefers-color-scheme: dark) {
:root {
/* anwendungsspezifische Anpassungen */
}
}
Drei Styling-Ebenen
Keluna sollte Styling auf drei Ebenen ermöglichen:
-
Framework Defaults
Minimale Standarddarstellung der Keluna-Komponenten. -
Application Theme
Globale Typografie, Farben, Abstände und Design-Tokens der Anwendung. -
Component Styles
Direkt bei einer fachlichen Komponente liegendes CSS für deren konkrete Darstellung.
Konzeptionell:
Keluna defaults
↓
Application theme
↓
Component-local CSS
Inline-Styles aus Kotlin
Keluna sollte einfache dynamische Style-Eigenschaften auch direkt aus Kotlin setzen können.
progressBar {
style.width = "${progress}%"
}
Das ist sinnvoll für Werte, die tatsächlich aus dem Anwendungszustand entstehen.
Statisches Design sollte dagegen im CSS bleiben.
Die Trennung sollte lauten:
Kotlin beschreibt Zustand und Verhalten; CSS beschreibt Design.
Zustandsabhängiges Styling
Anwendungszustände sollten möglichst über Klassen oder Attribute an CSS übergeben werden.
projectEntry {
cssClass("selected", selected)
attribute("data-status", project.status)
}
Daraus könnte entstehen:
<div class="project-entry selected" data-status="modified">
Das CSS bleibt dann deklarativ:
.project-entry.selected {
outline: 2px solid currentColor;
}
.project-entry[data-status="modified"] {
font-style: italic;
}
Dadurch muss Kotlin keine konkreten Farben, Abstände oder anderen Designentscheidungen kennen.
Kein JavaScript im normalen Anwendungscode
Das Styling-Konzept erfordert kein JavaScript vom Keluna-Anwendungsentwickler.
Die kleine JavaScript-Schicht, die Keluna für DOM-Synchronisation und Event-Weiterleitung benötigt, ist Bestandteil des Frameworks.
Für normale Anwendungen besteht der Stack damit aus:
Kotlin
+
CSS
↓
Keluna Runtime
↓
interne JavaScript-Bridge
↓
System-WebView
JavaScript bleibt ein möglicher Escape Hatch für spezielle Web-Komponenten oder externe Bibliotheken, ist aber keine Voraussetzung für die Entwicklung einer Keluna-Anwendung.
Designziel
Ein Feature besteht idealerweise aus:
ProjectBrowser.kt
ProjectBrowser.css
Kotlin enthält:
- Komponentenstruktur
- Zustand
- Events
- Anwendungslogik
CSS enthält:
- Layout
- Typografie
- Farben
- Animationen
- Responsive-Verhalten
- visuelle Zustände
Beides liegt direkt nebeneinander und bildet gemeinsam die Komponente.
Das erlaubt eine fachliche Projektstruktur statt einer technologischen Trennung und nutzt gleichzeitig die ausgereiften Styling-Fähigkeiten moderner WebViews.