Color 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 string

Available 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
end

Theme 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
end

Pseudo-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))}
end

Color 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:1

Examples

  • examples/demo.exs: Flagship demo using Synthwave '84 mapped to ANSI
  • examples/advanced/color_system_demo.ex: Color system with adaptive downsampling

Next Steps