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 thebootstylevocabulary. 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 —
cosmo,flatly,journal,litera,lumen,minty,pulse,sandstone,united,yeti,simplex,cerculean - Dark —
darkly,superhero,solar,cyborg,vapor
'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:
- Buttons — solid (just a colour),
outline,link,toolbutton,outline toolbutton(the toolbutton forms also work onCheckButtonas press-toggles). - Check buttons — solid colours, plus
round/squaretoggle switches (e.g.'success round'). - Labels — coloured text (solid) or
inversecolour-filled badges. - Progressbars — solid or
striped. - Scrollbars — solid or
round(slim pill thumb). - Entries, combo/spinboxes, scales, separators, notebooks, treeviews, menubuttons, frames — semantic colours (entry-likes colour the focus ring).
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
- Icons — the bundled Feather icon set, coloured from this palette
- Widget basics — creating widgets and setting options
- Layout —
pack,grid,place - Widget reference