Theming

A theme controls the look of every themed widget — which in ScriptWeaver is the default for all the standard controls (Button, Entry, CheckButton, …). Switch themes with a single call:

app.setTheme('flatly');

setTheme applies immediately and re-styles widgets that already exist, so you can let the user change the look at runtime. Read the active theme with app.theme — which you can also assign to (app.theme = 'flatly' is the same as app.setTheme('flatly')) — and list every built-in name with sw.app.themes:

const picker = app.Combobox({ state: 'readonly' });
picker.setValues(sw.app.themes); // every available theme name
picker.value = app.theme; // start on the current theme
picker.bind('<<ComboboxSelected>>', () => (app.theme = picker.value));

The themed widgets — the standard controls (Button, Entry, …) — restyle fully, including the bootstyle vocabulary. The classic Tk widgets (TkFrame, Listbox, Text, Canvas, …) keep their native widget behaviour but still follow the theme's base colours — see Classic widgets.

The built-in themes

ScriptWeaver bundles the Bootstrap-flavoured ttkbootstrap theme family. Pass any of these names to setTheme:

'light' and 'dark' are always-available aliases for the default pair, cerculean (light) / superhero (dark):

app.setTheme('dark'); // = superhero
app.setTheme('light'); // = cerculean

The bundled palettes are contrast-calibrated: body text and control affordances in every theme meet the WCAG AA ratios (4.5:1 / 3:1), and keyboard focus stays visible on every control.

Each theme is generated on first use and cached, so the initial setTheme for a given name does a little more work than later switches back to it.

Grouped names — sw.app.themesByType

sw.app.themes is the flat catalog (aliases included). When you need the light/dark split — a grouped picker, or pairing a light theme with its dark counterpart — use sw.app.themesByType:

const { light, dark } = sw.app.themesByType; // engine names only, no aliases

Following the system theme

sw.app.systemDark reports the OS-level appearance preference: true (dark), false (light), or null when the Player can't read it on the current desktop. The OS is asked once and the answer cached, so the supported pattern is a startup read:

const pair = { light: 'cerculean', dark: 'superhero' }; // your app's theme pair
const dark = sw.app.systemDark;
if (dark !== null) app.setTheme(dark ? pair.dark : pair.light);

Reacting to a live OS theme change is not supported yet; it is planned as an additive event, so apps written with the startup read stay correct.

On a desktop with no dark/light mode — just a fixed theme, like MATE — set SCRIPTWEAVER_THEME (below) to override systemDark with your chosen theme's darkness.

Choosing the theme at startup

The Player starts in dark by default. Pick another from the command line:

scriptweaver --light app.js         # cerculean
scriptweaver --dark app.js          # superhero (the default)
scriptweaver --theme cosmo app.js   # any theme, by name
scriptweaver --list-themes          # print the available names and exit

--theme <name> takes any name setTheme understands (the light/dark aliases and every theme above); an unknown name errors and lists the valid ones. --light / --dark are shorthands for --theme light / --theme dark.

SCRIPTWEAVER_THEME is the environment-variable form of --theme: a standing default used when no --theme/--dark/--light flag is given (a flag wins for that launch). It takes the same names and is validated the same way (an unknown name errors). Setting it also makes sw.app.systemDark report that theme's darkness — so on a desktop with no dark/light preference (e.g. MATE), export SCRIPTWEAVER_THEME=superhero makes the Player launch dark and follow-system apps (like the Designer) go dark. A one-off --theme/--dark flag changes only the launch theme, not the preference.

Pressing F1 (or calling sw.openHelp()) opens the help viewer in the app's current theme and UI scale — both app.theme and sw.app.uiScale are passed through to it — so help matches whatever the user is looking at.

UI scale (density)

Every theme ships at a comfortable, Bootstrap-faithful size — 12 pt text and roomy controls — which suits general-purpose apps and readability. Some apps want the opposite: an IDE, a data tool, or a settings pane with a long list of fields reads better denser, fitting more on screen; and some users simply want everything larger. sw.app.uiScale is the one knob for both:

sw.app.uiScale = 0.85; // denser — ~10 pt text, tighter padding (a classic-desktop feel)
sw.app.uiScale = 1.0; // the default
sw.app.uiScale = 1.25; // larger, e.g. for low vision

It is a single multiplier applied to font sizes, control padding and the Tk named fonts together, so the whole UI scales proportionally — it is not merely a font-size change. It composes on top of the automatic per-display DPI scaling (so HiDPI keeps working), is clamped to [0.5, 2.0], and re-styles existing widgets immediately. Reading it back returns the current factor; assigning re-applies the active theme at the new density.

End users can override an app's choice at launch with --ui-scale:

scriptweaver --ui-scale 0.85 app.js   # more content per screen

Set it once at startup for the whole app — there is no per-widget scale.

Semantic colours — the bootstyle option

Within a theme, themed widgets come in Bootstrap's semantic colours — primary, secondary, success, info, warning, danger, light, dark — and in variants like outlined or link-style buttons and toggle switches. The bootstyle option picks one with keywords (any order, case-insensitive, space- or dash-separated):

app.Button({ text: 'Save', bootstyle: 'success' });
app.Button({ text: 'Delete', bootstyle: 'danger outline' });
app.Button({ text: 'More…', bootstyle: 'info link' });
app.CheckButton({ text: 'Enabled', bootstyle: 'success round' }); // toggle switch
app.Progressbar({ value: 60, bootstyle: 'info striped' });
app.Label({ text: ' NEW ', bootstyle: 'info inverse' }); // badge

What exists per widget class (variants × colours) is enumerable as data at sw.theme.bootstyles, and a bad combination throws immediately with the valid choices — nothing fails silently into the default look. The variants at a glance:

You can also assign at runtime: btn.bootstyle = 'warning outline'.

bootstyle is sugar over the widget's style option — the resolved name for 'success outline' on a Button is success.Outline.TButton, and using style: directly continues to work (when both are given, style wins). Resolve names yourself with sw.theme.resolve('Button', 'success outline'); orientation-carrying classes get the widget's orient injected automatically ('striped' on a vertical progressbar resolves to Striped.Vertical.TProgressbar).

The palette — sw.theme.colors

The active theme's palette is readable at runtime — for drawing on a Canvas, colouring Text tags, or contrast checks:

sw.theme.color('primary'); // '#4c9be8' (in superhero)
const { bg, fg, selectbg, type } = sw.theme.colors; // fresh snapshot; type: 'light'|'dark'

Re-read after app.setTheme() — the values follow the active theme. Keys: the eight semantic colours plus bg, fg, selectbg, selectfg, border, inputbg, inputfg, and type.

Classic widgets and the theme

The classic Tk widgets — TkFrame, Listbox, Text, Canvas, Message — aren't ttk-styled, but ScriptWeaver still keeps their base colours in step with the theme. A classic widget you create without a colour option follows the palette automatically: it adopts the current theme on creation and re-colours on every later app.setTheme().

const list = app.Listbox(); // no colour given → follows the theme
app.setTheme('dark'); // …and re-colours to the dark palette here

Pass an explicit colour and the widget is yours — a theme change never overwrites it:

const c = app.Canvas({ background: '#101820' }); // stays #101820 across themes

To make an explicitly-coloured widget follow the theme anyway — or to adopt the palette on demand — call widget.applyTheme(). It applies the active theme's colours now and starts following future changes (a no-op on ttk widgets, which restyle natively):

c.applyTheme(); // adopt the current theme and track it from here on

Resolving styles and colours — app.styleLookup / app.rgb

For theme-aware custom drawing, read a ttk style's resolved option, or turn any Tk colour into channels:

app.styleLookup('TButton', 'background'); // a style's resolved option value
app.rgb('#375a7f'); // { r: 55, g: 90, b: 127 } — 0–255 channels
app.rgb(sw.theme.color('fg')); // resolve a palette colour for contrast checks

app.rgb accepts any Tk colour — a name ('red'), #rrggbb, or a hex from the palette above.

The window background

setTheme also syncs the root window's background to the new theme, so the area behind your widgets matches. If you place plain (non-T) widgets directly on the window, give them an explicit colour or wrap them in a Frame.

Next