TermUI.Theme (TermUI v1.0.0)

View Source

Theme system for application-wide visual consistency.

Themes define colors, semantic meanings, and component style defaults. The theme system supports runtime switching and notifies subscribers of changes.

TermUI.Runtime does not start or subscribe to this server. Without a running theme server, read helpers return the built-in dark theme. To switch themes, supervise TermUI.Theme, subscribe from an application-owned process, and cause the Elm root to update or force a render when a notification arrives.

Theme Structure

A theme contains:

  • :name - Theme identifier (e.g., :dark, :light)
  • :colors - Base colors (background, foreground, primary, etc.)
  • :semantic - Semantic colors (success, warning, error, etc.)
  • :components - Per-component style defaults

Built-in Themes

  • :dark - Dark background with light text (default)
  • :light - Light background with dark text
  • :high_contrast - High contrast for accessibility

Examples

# Start theme server
Theme.start_link(theme: :dark)

# Get current theme
theme = Theme.get_theme()

# Switch themes at runtime
Theme.set_theme(:light)

# Subscribe to theme changes
Theme.subscribe()
receive do
  {:theme_changed, new_theme} -> handle_change(new_theme)
end

# Get colors
bg = Theme.get_color(:background)
error = Theme.get_semantic(:error)

# Get component style
style = Theme.get_component_style(:button, :focused)

Summary

Functions

Returns a specification to start this module under a supervisor.

Creates a theme from a keyword list or map, merging with a base theme.

Gets a built-in theme by name.

Gets a base color from the current theme.

Gets a component style from the current theme.

Gets a semantic color from the current theme.

Gets the current theme.

Lists all available built-in themes.

Sets the current theme.

Starts the theme server.

Creates a style from theme values with optional overrides.

Subscribes the calling process to theme change notifications.

Unsubscribes the calling process from theme change notifications.

Validates a theme struct.

Types

color()

@type color() :: TermUI.Renderer.Style.color()

colors()

@type colors() :: %{
  background: color(),
  foreground: color(),
  primary: color(),
  secondary: color(),
  accent: color()
}

component_styles()

@type component_styles() :: %{
  required(atom()) => %{required(atom()) => TermUI.Renderer.Style.t()}
}

semantic()

@type semantic() :: %{
  success: color(),
  warning: color(),
  error: color(),
  info: color(),
  muted: color(),
  help: color(),
  placeholder: color()
}

t()

@type t() :: %TermUI.Theme{
  colors: colors(),
  components: component_styles(),
  name: atom(),
  semantic: semantic()
}

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

from(opts)

@spec from(keyword() | map()) :: {:ok, t()} | {:error, term()}

Creates a theme from a keyword list or map, merging with a base theme.

Options

  • :base - Base theme to merge with (default :dark)
  • :name - Theme name
  • :colors - Color overrides
  • :semantic - Semantic color overrides
  • :components - Component style overrides

Examples

# Create custom theme based on dark
{:ok, theme} = Theme.from(
  base: :dark,
  name: :my_theme,
  colors: %{primary: :magenta}
)

get_builtin(name)

@spec get_builtin(atom()) :: {:ok, t()} | {:error, :not_found}

Gets a built-in theme by name.

get_color(name, server \\ __MODULE__)

@spec get_color(atom(), GenServer.server()) :: color() | nil

Gets a base color from the current theme.

Examples

Theme.get_color(:background)  # => :black
Theme.get_color(:primary)     # => :blue

get_component_style(component, variant, server \\ __MODULE__)

@spec get_component_style(atom(), atom(), GenServer.server()) ::
  TermUI.Renderer.Style.t() | nil

Gets a component style from the current theme.

Examples

Theme.get_component_style(:button, :focused)
Theme.get_component_style(:text_input, :normal)

get_semantic(name, server \\ __MODULE__)

@spec get_semantic(atom(), GenServer.server()) :: color() | nil

Gets a semantic color from the current theme.

Examples

Theme.get_semantic(:error)    # => :red
Theme.get_semantic(:success)  # => :green

get_theme(server \\ __MODULE__)

@spec get_theme(GenServer.server()) :: t()

Gets the current theme.

list_builtin()

@spec list_builtin() :: [atom()]

Lists all available built-in themes.

set_theme(theme, server \\ __MODULE__)

@spec set_theme(atom() | t(), GenServer.server()) :: :ok | {:error, term()}

Sets the current theme.

Accepts a theme name atom (for built-in themes) or a Theme struct. Notifies all subscribers of the change.

Examples

Theme.set_theme(:light)
Theme.set_theme(%Theme{name: :custom, ...})

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts the theme server.

Options

  • :theme - Initial theme (atom name or Theme struct, default :dark)
  • :name - GenServer name (default Elixir.TermUI.Theme)

Examples

Theme.start_link(theme: :dark)
Theme.start_link(theme: :light, name: MyApp.Theme)

style_from_theme(component, variant, overrides \\ [], server \\ __MODULE__)

@spec style_from_theme(atom(), atom(), keyword(), GenServer.server()) ::
  TermUI.Renderer.Style.t()

Creates a style from theme values with optional overrides.

Useful for components that want to use theme defaults but allow customization.

Examples

# Use theme button style as base, override foreground
style = Theme.style_from_theme(:button, :normal, fg: :red)

subscribe(server \\ __MODULE__)

@spec subscribe(GenServer.server()) :: :ok

Subscribes the calling process to theme change notifications.

Subscribers receive {:theme_changed, theme} messages when the theme changes.

unsubscribe(server \\ __MODULE__)

@spec unsubscribe(GenServer.server()) :: :ok

Unsubscribes the calling process from theme change notifications.

validate(theme)

@spec validate(t()) :: :ok | {:error, [String.t()]}

Validates a theme struct.

Returns :ok if valid, {:error, reasons} if invalid.