GUI KSS Themes

# GUI Themes and KSS

Klyn GUI widgets are styled through KSS, a small CSS-like language used by `klyn.gui.windows`. The built-in light and dark themes define the default desktop look, while applications can force an appearance mode or install their own KSS stylesheet at startup.

Built-In Light and Dark Themes

The standard themes are stored under `KLYN_HOME/lib/klyn/gui/windows/themes` as `light.kss` and `dark.kss`. By default, windows use `ThemeManager.MODE_SYSTEM`, which maps to the operating-system appearance when Klyn can detect it. The screenshots below are generated from `samples/gui/Calculator.kn` with the proposed built-in KSS themes.

Calculator sample rendered with `light.kss`.
Calculator sample rendered with `dark.kss`.
Controlling Appearance Mode

Use `ThemeManager.setAppearanceMode()` when the whole application should follow a mode. Use `window.appearanceMode` when one window must override the global mode. Set the mode before creating and laying out the window when possible, because font and padding declarations can affect preferred sizes.

    import klyn.gui.windows
    import klyn.gui.windows.themes

    # Global choice for windows that keep MODE_SYSTEM.
    ThemeManager.setAppearanceMode(ThemeManager.MODE_DARK)

    win = MainWindow("Themed window")
    win.appearanceMode = ThemeManager.MODE_SYSTEM
    win.size = (480, 320)
    win.centerIn(null)
    win.show()
    win.run()

Valid modes are `ThemeManager.MODE_SYSTEM`, `ThemeManager.MODE_LIGHT`, and `ThemeManager.MODE_DARK`. A registered custom theme can also be selected by its name, for example `"harbor"`. On platforms where system detection is not available, `system` falls back to the light theme unless an environment hint is present.

Defining a Custom KSS Stylesheet

A custom stylesheet is an ordinary `.kss` file following the same syntax as the built-in themes. Load it at startup with `ThemeManager.setThemeFromFile()` and give it an application-specific name. This does not replace `light.kss` or `dark.kss`.

    import klyn.gui.windows
    import klyn.gui.windows.themes
    import klyn.io

    ThemeManager.setThemeFromFile(
        "harbor",
        FilePath(Path.cwd, "themes", "harbor.kss")
    )
    ThemeManager.setAppearanceMode("harbor")

Keep application themes separate from the standard library themes. The files under `KLYN_HOME/lib/klyn/gui/windows/themes` are the default GUI contract of the runtime; an application should ship its own KSS files next to its sources or in a dedicated resource directory.

KSS Syntax

KSS keeps CSS block syntax, selector names, and declarations, but deliberately supports only the subset consumed by Klyn widgets. Shared design tokens can be declared in `:root` as CSS-style variables and reused with `var(--name)` or `var(--name, fallback)`. This makes parsing cheap while keeping styles maintainable.

    :root {
        --font-ui: Segoe UI Variable Text, Segoe UI, DejaVu Sans, Arial, sans-serif;
        --font-size-base: 16px;
        --surface: #173B3F;
        --surface-strong: #0F2A2E;
        --surface-input: #FFF7E8;
        --text: #F8ECD7;
        --text-muted: #A8DADC;
        --text-on-input: #123235;
        --text-on-accent: #102A2E;
        --accent: #F4A261;
        --accent-alt: #2A9D8F;
        --accent-hover: #E9C46A;
        --border-soft: #40676B;
        --radius-card: 16px;
        --radius-control: 10px;
        --radius-button: 12px;
        --window-padding: 10px;
        --card-padding: 18px;
        --field-padding: 10px 14px;
        --button-padding: 11px 20px;
        --button-margin: 6px 0px;
    }

    Widget {
        font-family: var(--font-ui);
        font-size: var(--font-size-base);
        color: var(--text);
        accent-color: var(--accent);
    }

    MainWindow {
        background: var(--surface-strong);
        padding: var(--window-padding);
    }

    Container.preview-card {
        background: var(--surface);
        border-color: var(--accent-alt);
        border-width: 1px;
        corner-radius: var(--radius-card);
        padding: var(--card-padding);
    }

    Label.title {
        font-size: 24px;
        color: var(--text);
    }

    Label.muted {
        color: var(--text-muted);
    }

    TextBox {
        background: var(--surface-input);
        color: var(--text-on-input);
        border-color: var(--accent-hover);
        border-width: 2px;
        corner-radius: var(--radius-control);
        padding: var(--field-padding);
        selection-color: var(--accent);
        caret-color: var(--accent);
    }

    Button {
        background: var(--accent);
        color: var(--text-on-accent);
        border-color: var(--text);
        border-width: 0px;
        corner-radius: var(--radius-button);
        padding: var(--button-padding);
        margin: var(--button-margin);
    }

    Button.secondary {
        background: var(--accent-alt);
        color: var(--text);
    }

    Button:hover {
        background: var(--accent-hover);
    }

    Slider {
        color: var(--text);
        border-color: var(--border-soft);
        accent-color: var(--accent);
        track-fill-color: var(--accent-alt);
    }

Custom KSS preview rendered with the stylesheet above. It deliberately uses a teal and amber visual direction instead of extending the built-in light or dark themes.

Save the stylesheet above as `themes/harbor.kss`, then load it before constructing the widgets. The preview window can be written as follows:

    import klyn.gui.windows
    import klyn.gui.windows.layouts
    import klyn.gui.windows.themes
    import klyn.io

    ThemeManager.setThemeFromFile(
        "harbor",
        FilePath(Path.cwd, "themes", "harbor.kss")
    )
    ThemeManager.setAppearanceMode("harbor")

    class HarborThemePreview extends MainWindow:

        public HarborThemePreview():
            super("Custom KSS Preview")
            this.size = (560, 390)
            this.centerIn(null)

            root as Container = this.centralWidget
            rootLayout as VBoxLayout = VBoxLayout(spacing=14)
            root.layout = rootLayout

            cardLayout as VBoxLayout = VBoxLayout(spacing=12)
            card as Container = Container(cardLayout)
            card.styleClass = "preview-card"
            card.layoutParams = LayoutParams(1, 1)

            title as Label = Label("Harbor Control")
            title.styleClass = "title"
            cardLayout.add(title)

            subtitle as Label = Label("Teal surfaces, warm amber actions.")
            subtitle.styleClass = "muted"
            cardLayout.add(subtitle)

            field as TextBox = TextBox("captain@klyn.local")
            field.preferredHeight = 48
            cardLayout.add(field)

            slider as Slider = Slider(value=64, minimum=0, maximum=100)
            slider.showTicks = true
            slider.showValues = true
            slider.tickInterval = 25
            slider.preferredHeight = 86
            cardLayout.add(slider)

            rowLayout as HBoxLayout = HBoxLayout(spacing=12)
            row as Container = Container(rowLayout)
            row.preferredHeight = 54

            details as Button = Button("Details")
            details.styleClass = "secondary"
            rowLayout.add(details)

            launch as Button = Button("Launch")
            rowLayout.add(launch)

            cardLayout.add(row)
            rootLayout.add(card)

    window = HarborThemePreview()
    window.run()

Selectors

KSS selectors are resolved from general to specific. Later matching rules overlay earlier rules, so `Button.primary` can refine the base `Button` style.

- Widget selectors: `Widget`, `Window`, `MainWindow`, `Container`, `Canvas`, `Label`, `AbstractButton`, `Button`, `CheckBox`, `TextBox`, `ComboBox`, `SpinBox`, `Slider`, `DatePicker`, `Splitter`, `TreeView`, `TableView`, `LightBox`, `MenuBar`, `Menu`, `DockWorkspace`, `DockPane`, `DockBar`, `DockCenterArea`, and `TabWidget`.
- Style classes: set `widget.styleClass = "danger compact"`, then target `.danger`, `Button.danger`, or another widget-qualified class selector.
- Pseudo states: `:hover`, `:focus`, `:pressed`, and `:disabled`. The `:pressed` state currently applies to button-like widgets.

Supported Declarations

The parser accepts CSS-like names plus a few Klyn-specific names. Some declarations only have an effect on widgets whose painter consumes them.

Text and color

`font`, `font-family`, `font-size`, `color`, `text-color`, `placeholder-color`, `placeholder-text-color`, `selection-color`, `inactive-selection-color`, `alternate-row-color`, `muted-color`, `focus-ring-color`, and `caret-color`.

Surfaces, borders, and accents

`background`, `background-color`, `background-color-2`, `background-gradient-to`, `border-color`, `border-width`, `corner-radius`, `accent-color`, and `track-fill-color` for sliders. Scrollable and modal widgets can also consume `scrollbar-track-color`, `scrollbar-thumb-color`, `scrollbar-active-thumb-color`, and `overlay-color` (also accepted as `scrim-color`).

Layout metrics

`margin`, `margin-left`, `margin-top`, `margin-right`, `margin-bottom`, `padding`, `padding-x`, `padding-y`, `padding-left`, `padding-top`, `padding-right`, and `padding-bottom`.

Shadow fields

`shadow-color`, `shadow-blur`, `shadow-offset-x`, and `shadow-offset-y` are parsed into `ThemeStyle`. A widget only shows them when its painter explicitly implements shadow rendering.

Variables

Declare design tokens in `:root` with names such as `--accent` or `--radius-control`. Any declaration value can then use `var(--accent)`. Use `var(--missing, fallback)` when a variable is optional.

Using Style Classes

Use `styleClass` when a widget has a semantic role that should not require a new widget subclass. This keeps style decisions in KSS while preserving statically typed widget code.

    deleteButton = Button("Delete")
    deleteButton.styleClass = "danger"

    primaryButton = Button("Save")
    primaryButton.styleClass = "primary"

    Button.danger {
        background: #B42318;
        color: white;
    }

    Button.primary:focus {
        border-color: #7CC4FF;
    }

Practical Rules
Keep themes stable

Put default GUI language styling in the built-in light and dark themes. Put application identity, brand colors, and one-off visual decisions in an application KSS file or in the sample itself. This separation avoids changing every application when the standard widget theme evolves.

- Set global or custom themes before creating windows.
- Use `MODE_SYSTEM` for normal applications; reserve forced light/dark mode for demos, previews, or explicit user preferences.
- Prefer `accent-color` for interactive highlights so buttons, sliders, focus indicators, and selection colors stay coherent.
- Do not rely on unsupported CSS features such as cascading combinators, media queries, percentages, or arbitrary units. KSS intentionally stays small.

Next Step

Continue with FormLayout to build label/field forms with the themed widgets.