Files
keluna/docs/keluna-css-integration.md
2026-09-16 11:03:07 +02:00

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:

  1. Framework Defaults
    Minimale Standarddarstellung der Keluna-Komponenten.

  2. Application Theme
    Globale Typografie, Farben, Abstände und Design-Tokens der Anwendung.

  3. 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.