Theming
View SourceColor schemes and styling for terminal and LiveView apps.
Terminal Theming
Inline colors
The View DSL accepts colors directly via fg: and bg::
text("Hello", fg: :cyan) # Named ANSI color
text("Warning", fg: :yellow, style: [:bold]) # Bold yellow
text("Custom", fg: {255, 107, 174}) # RGB tuple
text("256-color", fg: 198) # 256-color palette index
text("Hex color", fg: "#ff6bae") # Hex stringAvailable named colors: :black, :red, :green, :yellow, :blue, :magenta, :cyan, :white.
Color by status
Pattern match to return colors based on data:
defp severity_color(:critical), do: :red
defp severity_color(:warning), do: :yellow
defp severity_color(:info), do: :cyan
defp severity_color(_), do: :white
# Usage:
text(message, fg: severity_color(level))Terminal capability detection
Raxol auto-detects terminal color support and downsamples:
- Truecolor (24-bit): RGB tuples and hex strings render exactly
- 256-color: RGB is mapped to the nearest 256-color value
- 16-color: Mapped to the closest ANSI color
- Mono: All colors stripped, styling preserved (bold, underline)
Raxol.Style.Colors.Adaptive.adapt_color_safe/1 handles this transparently.
Synthwave '84 palette example
The flagship demo uses Synthwave '84 Soft mapped to ANSI:
# Consistent color language across your app
defp accent, do: :cyan # Titles, active elements
defp highlight, do: :magenta # Key hints, borders
defp warn, do: :yellow # Warnings, headers
defp ok, do: :green # Success, healthy
defp err, do: :red # Errors, critical
# Usage:
text(" DASHBOARD ", fg: accent(), style: [:bold])
text(" q:quit Tab:switch ", fg: highlight())
text("CPU: #{pct}%", fg: if(pct > 90, do: err(), else: ok()))Themed panels
Create a reusable panel helper:
defp panel(title, opts \\ []) do
border = Keyword.get(opts, :border, :single)
active = Keyword.get(opts, :active, false)
children = Keyword.get(opts, :children, [])
box style: %{border: (if active, do: :double, else: border), flex: 1} do
column do
[text(" #{title} ", fg: :cyan, style: [:bold]) | children]
end
end
endTheme System
ThemeManager
Switch themes at runtime:
# Switch to a built-in theme
Raxol.UI.Theming.ThemeManager.set_theme(:nord)Component-level theming
Components read theme styles via Raxol.UI.Theming.Theme.component_style/2. In a TEA module, apply theme styles in view/1:
def view(model) do
theme = Raxol.current_theme()
btn_style = Raxol.UI.Theming.Theme.component_style(theme, :button)
column do
[
text("Submit", fg: btn_style[:fg] || :cyan, style: [:bold])
]
end
endPseudo-state styles
Themes can define styles for :focus, :active, and :disabled states:
component_styles: %{
button: %{
fg: :cyan,
focus: %{fg: :white, bg: :blue, bold: true},
active: %{fg: :black, bg: :cyan},
disabled: %{fg: :white, dim: true}
}
}The FocusHelper module resolves the correct style based on Component state.
Built-in themes
Themes are stored as JSON in priv/themes/:
ls priv/themes/
# Default.json
LiveView Theming
When using the LiveView bridge, theming is applied via CSS classes on the terminal container div. Set a data-theme attribute (or a theme CSS class) on the container element, then define per-theme CSS rules targeting that class:
<div id="terminal" class="terminal" data-theme="nord">
<!-- terminal content rendered here -->
</div>Built-in LiveView themes: :default, :light, :nord, :dracula, :synthwave84.
Custom CSS theme
The following is an example custom CSS theme based on the Synthwave84 palette:
.terminal.theme-custom {
background-color: #1a1a2e;
color: #e0e0e0;
}
.terminal.theme-custom .fg-cyan {
color: #40c4ff;
}
.terminal.theme-custom .fg-magenta {
color: #ff6bae;
}
.terminal.theme-custom .fg-green {
color: #00ff9d;
}
.terminal.theme-custom .fg-yellow {
color: #ffd700;
}
.terminal.theme-custom .fg-red {
color: #ff5555;
}
.terminal.theme-custom .cursor {
background-color: #40c4ff;
}Dynamic theme switching
def handle_event("change_theme", %{"theme" => theme}, socket) do
{:noreply, assign(socket, theme: String.to_existing_atom(theme))}
endColor Palettes
Popular palettes mapped to ANSI for quick reference:
Nord
red: #bf616a, green: #a3be8c, yellow: #ebcb8b, blue: #81a1c1, magenta: #b48ead, cyan: #88c0d0
Dracula
red: #ff5555, green: #50fa7b, yellow: #f1fa8c, blue: #bd93f9, magenta: #ff79c6, cyan: #8be9fd
Tokyo Night
red: #f7768e, green: #9ece6a, yellow: #e0af68, blue: #7aa2f7, magenta: #ad8ee6, cyan: #449dab
Catppuccin Mocha
red: #f38ba8, green: #a6e3a1, yellow: #f9e2af, blue: #89b4fa, magenta: #f5c2e7, cyan: #94e2d5
Accessibility
High contrast
Use maximum contrast ratios. Avoid relying on color alone:
# Bad: color is the only indicator
text("OK", fg: :green)
text("FAIL", fg: :red)
# Good: text + color
text("[OK] Passed", fg: :green)
text("[!!] FAILED", fg: :red, style: [:bold])WCAG contrast checking
Ensure foreground/background pairs meet WCAG AA (4.5:1 ratio):
# High contrast pairs that work everywhere:
text("...", fg: :white, bg: :black) # 21:1
text("...", fg: :black, bg: :green) # ~5.5:1
text("...", fg: :black, bg: :cyan) # ~8.6:1
text("...", fg: :black, bg: :yellow) # ~10.2:1Examples
examples/demo.exs: Flagship demo using Synthwave '84 mapped to ANSIexamples/advanced/color_system_demo.ex: Color system with adaptive downsampling
Next Steps
- Building Apps: TEA patterns and recipes
- SSH Deployment: Serve apps over SSH
- Performance: 60fps techniques