Selkie.git | docs/api/ | Selkie--Theme.md
NAME
====
Selkie::Theme - Named palette of `Selkie::Style` slots
SYNOPSIS
========
```raku
use Selkie::Style;
use Selkie::Theme;
# Build a custom theme
my $theme = Selkie::Theme.new(
base => Selkie::Style.new(fg => 0xFFFFFF, bg => 0x000000),
text => Selkie::Style.new(fg => 0xEEEEEE),
text-dim => Selkie::Style.new(fg => 0x888888),
text-highlight => Selkie::Style.new(fg => 0xFFFFFF, bold => True),
border => Selkie::Style.new(fg => 0x444444),
border-focused => Selkie::Style.new(fg => 0x00FF00, bold => True),
input => Selkie::Style.new(fg => 0xEEEEEE, bg => 0x111111),
input-focused => Selkie::Style.new(fg => 0xFFFFFF, bg => 0x222222),
input-placeholder => Selkie::Style.new(fg => 0x666666, italic => True),
scrollbar-track => Selkie::Style.new(fg => 0x333333),
scrollbar-thumb => Selkie::Style.new(fg => 0x00FF00),
divider => Selkie::Style.new(fg => 0x444444),
);
# Use it app-wide
my $app = Selkie::App.new(:$theme);
```
DESCRIPTION
===========
A theme is a collection of named `Selkie::Style` slots — one per visual role in the framework. Widgets look up slots by name (`self.theme.border`, `self.theme.text-highlight`, etc.) so you can restyle the whole app by providing a different theme.
Themes are inherited through the widget tree: a widget's effective theme is its own explicit theme (if set), otherwise its parent's, falling back to `Selkie::Theme.default`.
EXAMPLES
========
Default theme
-------------
If you don't pass one to `Selkie::App`, the built-in dark palette is used:
```raku
my $app = Selkie::App.new; # uses Selkie::Theme.default
```
Scoping a different theme to a subtree
--------------------------------------
Give a specific panel a different palette without affecting the rest of the app:
```raku
my $warning-theme = Selkie::Theme.default;
# (imagine we mutated this or built a fresh one with red borders)
$warning-panel.set-theme($warning-theme);
```
Runtime theme swap
------------------
Subscribe a root container to a store path that holds the current theme, and swap at will:
```raku
$store.register-handler('theme/toggle', -> $st, % {
my $dark = $st.get-in('dark') // True;
(db => { dark => !$dark },);
});
$store.subscribe-with-callback(
'theme',
-> $s { $s.get-in('dark') // True },
-> Bool $dark { $root.set-theme($dark ?? dark-theme() !! light-theme()) },
$root,
);
```
SLOTS
=====
Every slot is a `Selkie::Style`. The base UI slots are `is required` — a theme must explicitly define every one. Chart slots have backward-compatible defaults derived from the base UI slots, so themes predating the chart widgets keep working without modification.
Required (UI core)
------------------
* `base` — default background plus text color
* `text`, `text-dim`, `text-highlight` — normal, subdued, and emphasised text
* `border`, `border-focused` — borders when unfocused vs containing the focused widget
* `input`, `input-focused`, `input-placeholder` — text input states
* `scrollbar-track`, `scrollbar-thumb` — vertical scrollbar
* `divider` — the split bar in `Selkie::Layout::Split`
* `tab-active`, `tab-inactive` — active and inactive tabs in `Selkie::Widget::TabBar`
Defaulted UI slots
------------------
These slots are used by controls and framework overlays. They default from the required core slots so existing themes keep working, but 1.0 themes should override them when they need a fully custom palette.
* `control-focused` — focused button/checkbox style
* `selection` — selected text and caret inversion style
* `input-ghost` — inline ghost-suggestion text in `Selkie::Widget::TextInput`
* `dropdown`, `dropdown-highlight` — Select dropdown surface and cursor row
* `overlay-title`, `overlay-key` — modal/overlay headings and key names
* `modal-backdrop` — dim plane behind modals
* `modal-frame`, `modal-title`, `modal-key` — a framed `Selkie::Widget::Modal`'s frame glyphs, top title, and bottom key-hint strip (default: `border`, `overlay-title`, `overlay-key`)
* `modal-scrim` — the colour a `BackdropScrim` modal tints the screen with (default: black at `AlphaBlend` on both channels)
* `tab-focus-accent` — accent merged onto a `FocusColor` `Selkie::Widget::TabBar`'s active tab while the bar is focused (default: `border-focused`)
* `toast` — default transient toast style
* `password-empty`, `password-weak`, `password-fair`, `password-good`, `password-strong`, `password-very-strong` — password-strength meter bands
Chart slots (defaulted, override for chart-rich apps)
-----------------------------------------------------
Used by `Selkie::Widget::Axis`, `Selkie::Widget::Legend`, and the chart family (`Sparkline`, `Plot`, `BarChart`, `Histogram`, `Heatmap`, `ScatterPlot`, `LineChart`). Defaults derive from the required slots so existing themes work as-is; override these for a distinct chart palette.
* `graph-axis` — axis line and tick marks (default: `text-dim`)
* `graph-axis-label` — tick labels (default: `text-dim`)
* `graph-grid` — optional gridlines behind chart bodies (default: `divider`)
* `graph-line` — single-series line/sparkline color (default: `border-focused`)
* `graph-fill` — fill-below color in line charts (default: `border-focused`; consider a darker shade)
* `graph-legend-bg` — legend pane background (default: same bg as `base`)
Multi-series colors are *not* theme slots — see [Selkie::Plot::Palette](Selkie--Plot--Palette.md) for the colorblind-safe series palettes (`okabe-ito`, `tol-bright`, `tableau-10`) and color ramps (`viridis`, `magma`, `plasma`, `coolwarm`, `grayscale`) used by chart widgets.
Custom slots
------------
Extend a theme with project-specific slots via the `%.custom` hash. Widgets can look them up via `self.theme.slot('my-slot-name')`:
```raku
my $theme = Selkie::Theme.new(
...,
custom => {
'chat-bot' => Selkie::Style.new(fg => 0x9ECE6A),
'chat-user' => Selkie::Style.new(fg => 0x7AA2F7),
},
);
# In a custom widget:
self.apply-style(self.theme.slot('chat-bot'));
```
`slot` falls back to the `base` style if the name isn't registered.
### has Selkie::Style $.base
The base background and default text color for the theme.
### has Selkie::Style $.border
Border style for unfocused borders.
### has Selkie::Style $.border-focused
Border style for borders whose descendant has focus. Auto-applied by `Selkie::Widget::Border` based on store focus state.
### has Selkie::Style $.text
Default text style.
### has Selkie::Style $.text-dim
Subdued text — for captions, help text, placeholder-ish content.
### has Selkie::Style $.text-highlight
Emphasised text — selected list items, highlighted values.
### has Selkie::Style $.input
Text input style when unfocused.
### has Selkie::Style $.input-focused
Text input style when focused.
### has Selkie::Style $.input-placeholder
Placeholder text style (shown when an input is empty and unfocused).
### has Selkie::Style $.scrollbar-track
Scrollbar track (the background rail).
### has Selkie::Style $.scrollbar-thumb
Scrollbar thumb (the filled bar showing position).
### has Selkie::Style $.divider
Divider line in `Selkie::Layout::Split`.
### has Selkie::Style $.tab-active
Active tab in `Selkie::Widget::TabBar`. Distinct background so the selected tab is unambiguously different from the rest — bracket decorators alone aren't enough contrast at a glance.
### has Selkie::Style $.tab-inactive
Inactive tabs in `Selkie::Widget::TabBar`.
### has Selkie::Style $.tab-focus-accent
Accent merged onto the active tab of a `Selkie::Widget::TabBar` running in `FocusColor` mode while the bar holds focus — the colour that mode spends instead of the `▶ ` prefix's two columns. Defaults to `border-focused`, so a bar opted into `FocusColor` picks up the same accent the rest of the chrome uses for focus without the theme having to say anything.
### has Selkie::Style $.control-focused
Focused button / checkbox style. Defaults to `tab-active` so existing themes inherit an obvious active-control treatment.
### has Selkie::Style $.selection
Text-selection and caret style. Defaults to a reverse-like style based on focused input colors.
### has Selkie::Style $.input-ghost
Inline ghost-suggestion text painted after the caret by `Selkie::Widget::TextInput` when a `suggest-provider` is set. Foreground-only by default — the input merges this onto its own style, so the field background shows through. Defaults to the placeholder foreground, italicised.
### has Selkie::Style $.dropdown
Select dropdown surface and normal row style. Defaults to `input` so existing themes keep the same compact-control background.
### has Selkie::Style $.dropdown-highlight
Select dropdown cursor row style. Defaults to `text-highlight` over the dropdown surface, preserving the surface background unless a theme opts into a stronger cursor treatment.
### has Selkie::Style $.overlay-title
Modal and overlay title style. Defaults to focused border color.
### has Selkie::Style $.overlay-key
Key label / command grouping style inside overlays.
### has Selkie::Style $.modal-backdrop
Full-screen dim plane behind modal content.
### has Selkie::Style $.modal-frame
Frame glyphs of a framed `Selkie::Widget::Modal`. Defaults to `border`, so a framed modal looks like any other panel until a theme gives modal chrome a colour of its own.
### has Selkie::Style $.modal-title
Top title of a framed `Selkie::Widget::Modal`. Defaults to `overlay-title`.
### has Selkie::Style $.modal-key
Bottom title of a framed `Selkie::Widget::Modal` — the key-hint strip. Defaults to `overlay-key`.
### has Selkie::Style $.modal-scrim
Colour a `BackdropScrim` modal tints the screen behind it with. Both channels default to `AlphaBlend`, which mixes this colour 50/50 with whatever the compositor has accumulated underneath — see [Selkie::Alpha](Selkie--Alpha.md). **There is no fractional alpha**: a deeper dim is a darker `fg`/`bg` here, never "more alpha". Black at blend is as dark as one scrim layer gets.
### has Selkie::Style $.toast
Default transient toast style.
### has Selkie::Style $.password-empty
Empty password-strength meter band.
### has Selkie::Style $.password-weak
Weak password-strength meter band.
### has Selkie::Style $.password-fair
Fair password-strength meter band.
### has Selkie::Style $.password-good
Good password-strength meter band.
### has Selkie::Style $.password-strong
Strong password-strength meter band.
### has Selkie::Style $.password-very-strong
Very strong password-strength meter band.
### has Selkie::Style $.graph-axis
Axis line and tick-mark style for chart widgets. Defaults to `text-dim` so existing themes inherit a reasonable look without needing to define this slot. Override for a distinct chart axis color.
### has Selkie::Style $.graph-axis-label
Tick label style for chart axes. Defaults to `text-dim`.
### has Selkie::Style $.graph-grid
Optional gridline style for chart bodies (used by `LineChart` and `ScatterPlot` when grids are enabled). Defaults to `divider`.
### has Selkie::Style $.graph-line
Default series color for single-series chart widgets (`Sparkline`, single-series `LineChart`). Multi-series widgets pull colors from `Selkie::Plot::Palette` instead of this slot. Defaults to `border-focused`.
### has Selkie::Style $.graph-fill
Fill-below color for `LineChart` when fill is enabled. Defaults to `border-focused`; for visual depth set this to a darker shade of `graph-line`.
### has Selkie::Style $.graph-legend-bg
Background style for `Selkie::Widget::Legend`. Defaults to a style with the same background as `base`, so legends blend by default. Override with a contrasting bg for a distinct legend pane.
### has Associative[Selkie::Style] %.custom
Extra application-specific slots. Keyed by name, values are `Selkie::Style`. Look them up via `slot(name)`.
### method slot
```raku
method slot(
Str:D $name
) returns Selkie::Style
```
Fetch a named custom slot, falling back to `base` if the name isn't registered. Useful for app-specific categories of styling that don't fit the built-in slots.
### method default
```raku
method default() returns Selkie::Theme
```
The built-in dark theme. Used automatically by `Selkie::App` when no theme is provided. Browse the implementation for exact colors — it's a cool blue-grey palette with accent on 0x7AA2F7.