Skip to content

Customizable schema

The customizable field on a theme is a map from dotted theme paths to field descriptors. The library renders one editor per entry inside the Settings system window, applies user overrides on top of the theme defaults, and persists them through the storage adapter.

customizable: {
"palette.accent": {
kind: "color-from-palette",
label: "Accent",
options: ["#5cb6b9", "#7c66f5", "#a855f7", "#ec4899"],
},
"shape.windowRadius": {
kind: "range",
label: "Window radius",
min: 0,
max: 24,
step: 2,
unit: "px",
},
"chrome.dockPosition": {
kind: "select",
label: "Dock position",
options: [
{ value: "bottom", label: "Bottom" },
{ value: "left", label: "Left" },
{ value: "hidden", label: "Hidden" },
],
},
}
  • Keys are dotted paths into OsTheme. The library walks them to write the override.
  • A theme that omits customizable (or sets it to {}) exposes no Settings panel.
  • Stale prefs (paths the theme later removed) are silently ignored.
KindEditorRequired fields
color-from-paletteA row of color swatches.options: string[] (hex / CSS colors)
image-pickA thumbnail grid.options: Array<{ src: string; label: string }>
rangeA slider with a tabular-numeric readout.min, max, step; optional unit
selectA segmented control.options: Array<{ value: string; label: string }>
toggleAn iOS-style switch (role="switch").(none)

Every kind also accepts label: string, description?: string, and section?: string.

"palette.accent": { section: "Appearance", … },
"shape.windowRadius": { section: "Appearance", … },
"motion.genieDurationMs":{ section: "Motion", … },
"chrome.dockPosition": { section: "Layout", … },

Each section becomes a category in the Settings panel: a sidebar that the user clicks or arrow-keys through, folding into a horizontal bar when the window is narrow. A search field above the categories filters across every section at once (matching a field’s label, description, or section name), so a setting is reachable no matter which category it lives in. A field with no section falls under “General”.

defaults < declared theme < user prefs

At runtime the active theme is applyPrefs(baseTheme, prefs). The function walks each pref path, shallow-clones along the way, and writes the leaf, leaving everything else untouched. The library re-runs this on every pref change.

From any component:

import { useSettings } from "@react-ui-os/desktop";
const { schema } = useSettings();
const fieldsByCategory = Object.entries(schema).map(([path, def]) => ({
path,
category: path.split(".")[0],
...def,
}));

The full hook surface is in useSettings.