Guppy themes are app-scoped Elixir data. They intentionally compile semantic tokens to primitive IR style tuples before native render so Elixir remains the source of truth and the Rust/GPUI bridge stays a typed renderer rather than a theme runtime.
Reference audit
Zed's theme system is the primary reference:
../zed/crates/theme/src/theme.rsdefinesThemeFamily,Theme,Appearance,GlobalTheme, and active-theme access.../zed/crates/theme/src/registry.rskeeps registered theme families in a global registry and looks themes up by display name.../zed/crates/theme/src/styles/colors.rsdefines a large semantic color surface (background,surface_background,text,border, editor/status colors, etc.).../zed/crates/theme/src/default_colors.rsfills missing theme colors from light/dark defaults.../zed/crates/theme_settings/src/theme_settings.rsandsettings.rsconnect settings, theme selection, overrides, andobserve_globalreloads.../zed/crates/theme_settings/src/schema.rsshows serialized theme-family/theme content and style overrides.
Relevant GPUI 0.2.2 hooks:
../zed/crates/gpui/src/colors.rsexposesColors,GlobalColors, andDefaultColorsfor GPUI's base component colors.../zed/crates/gpui/src/app.rssupportsset_global,default_global, andobserve_globalfor app-global data.../zed/crates/gpui/src/platform.rsexposesWindowAppearancelight/dark/vibrant variants.
Guppy does not currently install GPUI globals for themes. That would make native retained state partly responsible for theme resolution. The current layer stays app-scoped and Elixir-side.
Current Guppy shape
Guppy.App.Theme contains:
idnameappearance(:light,:dark, or:system)colors— semantic color tokens keyed by strings/atoms, with named Guppy colors or hex strings as valuesstyles— semantic style tokens resolved to canonical IR style tuplesmetadata
Theme style definitions can mix Guppy class strings, canonical style tuples, and explicit theme color references (Guppy.Style.theme_color/2 or Guppy.App.Theme.color_ref/2):
%{
id: "demo-dark",
name: "Demo Dark",
appearance: :dark,
colors: %{surface: "#0f172a", text: :white},
styles: %{
card: [
"p-4 rounded-lg",
Guppy.Style.theme_color(:bg, :surface),
Guppy.Style.theme_color(:text_color, :text)
]
}
}Validated themes store normalized string keys and resolved primitive style lists. Built-in defaults are available with Guppy.App.Theme.default(:dark) and Guppy.App.Theme.default(:light). Use Guppy.App.Theme.refine/2 to create explicit refinements/overrides from an existing theme while preserving unspecified tokens.
Guppy.App.ThemeFamily groups related themes by stable theme id. Apps can register families with Guppy.App.register_theme_family/2 and activate a registered theme by id with Guppy.App.set_theme/2.
App/window helpers
Use Guppy.active_theme/0..1 to inspect the current theme and Guppy.App for app-scoped theme mutation/lookup:
Guppy.active_theme()
Guppy.active_theme(app)
Guppy.App.set_theme(app, theme_or_registered_theme_id)
Guppy.App.theme_color(:surface)
Guppy.App.theme_color(app, :surface)
Guppy.App.theme_style(:card)
Guppy.App.theme_style(app, :card)
Guppy.App.register_theme_family(app, family)
Guppy.App.theme_families(app)use Guppy.Window imports current-app helpers through Guppy.Component:
def render(window) do
card_style = theme_style!(:card)
~GUI"""
<div id="card" style={card_style}>
<text>Themed card</text>
</div>
"""
endexamples/multi_window_app.exs demonstrates app-owned theme metadata, theme style resolution, stylesheet refs, commands, menus, keymaps, a command palette, and multi-window supervision.
Boundaries
- Raw template
stylestrings remain rejected; useclassstrings or resolved theme styles. - Theme resolution is Elixir-side and produces existing primitive style tuples.
- Native
GlobalColors/DefaultColorshooks are audited but not used for Guppy theme resolution yet. - Standalone non-app theme conveniences remain deferred until a concrete standalone example needs them.