Vortex 2.5 is archived. The latest version is 3.0. Read the 3.0 docs

Learn how Vortex interfaces are declared, styled, and themed with Layers JSON.

Styling Guide - Vortex

Vortex interfaces are declared in Layers JSON. Styles are not decoration applied to widgets built elsewhere. Styles are the structure: a style names the widgets, nests them, arranges them, and colors them. C++ code supplies behavior and data; JSON supplies everything you see. (In Layers, a style is just a named node of data; Vortex is what makes styles mean UI.)

This guide covers the authoring model. For the Layers JSON syntax itself (links, inheritance, key-expansion, states, grouping), see the Layers JSON guide.

Widgets come from inheritance

The framework ships a library of widget styles in styles/: one file per widget, v<widget>.json for primitives (vbox.json, vtext.json, vbutton.json, …) plus composite building blocks (window.json, titlebar.json, vsettingsmenu.json, …).

The framework's own styles load under the Vortex namespace (styles/widgets/ → Vortex/Box, Vortex/Button, …), so within the library they reference each other by bare name. A node's widget type is decided by the primitive its style ultimately inherits. This is the framework's Button:

{
    "Button": {
        "->": "S:Box",
        "Layout.Direction": "Row",
        "Layout.Spacing": 4,
        "Fill": "L:Theme/Tertiary",
        "Corner Radii.(Top,Bottom) (Left,Right)": 6,
        "Padding.(Bottom,Left,Right,Top)": 10,
        "Text Color": "L:Theme/Foreground"
    }
}

Three reference kinds do the work:

  • "->": "S:<Style>" is inheritance: this style adopts everything from the referenced style (structure included) and overrides on top. Bare within a namespace (S:Box); an app reaches across into the library with a qualified base (S:Vortex/<Style>).
  • "S:<Style>" as a child value is composition: embed another style as a child widget (same namespace rules).
  • "L:Theme/<Attribute>" is a theme link: it resolves to the framework's shared Theme node (a global mount kept out of any namespace, so it resolves from anywhere), which the active theme's palette drives (see Themes).

Declaring an app's interface

An app's styles load additively on top of the framework library and un-namespaced, so they inherit and compose any library style through a Vortex/-qualified reference. The root convention is an "App" style listing the app's top-level widgets:

{
    "App": {
        "->": "S:Vortex/Box",
        "Layout.Direction": "Column",
        "Layout.Align": "Stretch",
        "Main Window": {
            "->": "S:Vortex/Main Window",
            "Layout.Grow": 1
        },
        "Theme Directories Dialog": "S:Vortex/Theme Directories Dialog"
    }
}

build_app() activates it with app.set_root_style("App"); the platform shells pass the app's style directories alongside the framework's (see the app development guide).

Path appendages

Inherited structure would be useless if you couldn't reach inside it. A style whose key is a path overlays the node at that path (overriding its attributes, links, and state values, or adding children that don't exist yet), even when that node was materialized by S: inheritance:

{
    "App/Main Window/Content/Greeting": {
        "->": "S:Vortex/Text",
        "Text": "Hello, Vortex"
    }
}

This is how an app customizes the framework's shell without copying it: inherit Main Window (under its own window name, if it likes), then append and override by path. Child ordering is preserved through the merge, so appended nodes land where layout expects them.

States and variants

Any attribute can carry per-state values. The resolver folds in the active states (platform, window, and interaction), and the most specific match wins:

"Border.Thickness": {
    "->": 0,
    ":Desktop": 8,
    ":Desktop:Maximized": 0
}

States the framework drives for you:

  • Platform: :Desktop, :Web, :Mobile (set by each platform shell).
  • Window: :Maximized; :Narrow below the responsive width breakpoint; :ControlsHidden while chrome is hidden away on scroll.
  • Widget: :Selected (titlebar tabs), :Checked (checkboxes), :Expanded (collapsible drawers), :Open (sliding sheets), and per-row :First/:Last inside generated lists.

App code can toggle its own named states with app.toggle_state(path, state).

On mobile, resolved pixel values are additionally auto-scaled (icons 1.5×, fonts 1.25×, other metrics 1.25× by default), unless an attribute has an explicit :Mobile override, which wins verbatim. See the layout guide.

Themes

Theming is a Vortex feature built on Layers' indirection-node pattern; Layers itself has no notion of a theme. A theme is a small JSON file of five color attributes plus metadata:

{
    "Dark": {
        "_meta": {
            "publisher": "huntrsoftwarellc",
            "type": "dark"
        },
        "Foreground": "#e3e3e3",
        "Gradient": ["0:#3a3c42", "1:#42454d"],
        "Primary": "#36393f",
        "Secondary": "#2f3136",
        "Tertiary": "#25272b"
    }
}

Those five attributes are the canonical palette: the vocabulary every style links to (L:Theme/<Attribute>):

Attribute Purpose Typical elements
Foreground Strong contrast against all backgrounds Icons + text
Gradient Accent surface for borders/emphasis Window & dialog borders
Primary Main backgrounds Windows, dialogs, pages
Secondary Sub-element backgrounds Headers, sidebars, sections
Tertiary Interactive backgrounds Buttons, tabs, text editors, slider handles

Themes are identified as "<Name> (<publisher>)", e.g. "Dark (huntrsoftwarellc)". The framework bundles Dark and Light plus accent themes (Blue, Green, Indigo, Orange, Red, Violet, Yellow).

Every L:Theme/... link in every style resolves once, at load, to a shared Theme node (styles/vtheme.json). Switching themes is therefore a core-only, O(5) operation: app.set_active_theme(id) copies the chosen theme's five attributes onto that node, and every link already pointing at it picks up the new palette on the next render, with no per-link re-resolution and no per-platform code. The framework's settings menu lists loaded themes and switches on click, out of the box.

Themes are also runtime data: add_theme_json() / remove_theme() load and drop user themes, the shell's theme-directories dialog lets users point at their own theme folders, and each platform persists the active selection (QSettings, browser storage, SharedPreferences).

This section is the developer's account. The user-facing walkthrough (write a theme file, load it into any Vortex app, iterate on it) is the Custom Themes guide, which is also where the dialog's Learn More button lands.

Icons and media color

  • Icon widgets load a flat SVG by name and recolor it to the style's Text Color: one asset, every theme.
  • Image widgets showing an SVG render it as a live vector; if the style sets Text Color, the image recolors like an icon, otherwise its own colors are preserved. Raster images and video always render verbatim.

Apps override or extend the shell's icon set by shipping their own icon directory, searched before the framework's (see the app development guide).

Appearance
Theme
—