Docs


Themes Tutorial

Rete supports custom themes defined as JSON files. Themes provide UI colors and message colors for both Light and Dark variants. Users can import themes from Preferences or by placing files in the Themes folder.

Changelog

2026-03-05

2025-12-16

2025-12-15

2025-12-01

2025-11-27

2025-11-26

Where to put theme files

Notes:

JSON schema (overview)

Top-level object (Theme):

ThemePalette:

Fonts (optional):

UI (all colors are HexColor objects):

MessageColors:

HexColor:

Example HexColor: { "hex": "#0A84FF" }

Fallback behavior

If a color is missing or invalid:

Message color resolution:

  1. Category color is checked first (message.category.rawValue) - this is the primary classification
  2. Tag colors are checked only for contextual tags (highlight, bold, server, channel, dm, raw, in, out, script, do) that don’t map to categories
  3. If neither category nor tag color is defined, the UI foreground color is used
  4. Final fallback is Color.primary

Important: Categories and tags no longer overlap. Categories handle message types (join, part, mode, etc.), while tags only provide contextual metadata (where the message appears, formatting, etc.). This eliminates confusion about which to use for coloring.

Segment-based message styling

State messages (join, part, quit, topic, mode, kick, account, CTCP, nick change, etc.) are built from segments: each part (nick, channel, reason, topic text, user@host, account, timestamp, command name, etc.) is tagged with a segment kind. The theme can then color and bold those parts independently via segmentColors and segmentBold, and style the parentheses/brackets around them via segmentBraceColor.

Segment kinds

Key Used for
literal Plain text (e.g. “ has joined “, “ has left “)
channel Channel names (e.g. #foo); also styled by accent for clickability
person Nicks; use nick color rules when in channel, else segment style; often bold
topic Topic text (e.g. after “Topic for #ch: “)
quitmessage Quit reason text (default e.g. burgundy on red quit line)
partmessage Part reason inside ( ) (default colour complements part line)
kickmessage Kick reason inside ( ) (default colour complements kick line)
hostmask user@host inside ( ) in join/part/quit
account Account name inside [ ]
timestamp Dates/times (e.g. “channel created on”, “topic set on”)
mode, modeParam Mode string and parameters in mode messages
command CTCP command and “request”/”reply” in CTCP messages
value Other values (e.g. batch params in ( ))

Brace color

segmentBraceColor applies to the characters (, ), [, ] that wrap segments (e.g. reason in parentheses, account in brackets). Default is a subtle grey so braces don’t contrast with the content; you can set it to match your palette (e.g. same as divider or a muted foreground).

Example: custom segment and brace colors

{
  "messageColors": {
    "categories": { "join": { "hex": "#34C759" }, "part": { "hex": "#FF9F0A" }, ... },
    "tags": { ... },
    "segmentColors": {
      "topic": { "hex": "#007AFF" },
      "quitmessage": { "hex": "#800020" },
      "partmessage": { "hex": "#8B6914" },
      "kickmessage": { "hex": "#8B2500" },
      "hostmask": { "hex": "#6B7280" },
      "account": { "hex": "#00CED1" }
    },
    "segmentBold": ["person", "timestamp", "command"],
    "segmentBraceColor": { "hex": "#8E8E93" }
  }
}

If you omit segmentColors, segmentBold, or segmentBraceColor, the app uses semantic defaults so state messages still look distinct and readable.

Appearance mode

The app supports:

The ThemeManager resolves the active palette at runtime. The theme must define both light and dark palettes.

Example: Minimal valid theme

This is a tiny example that uses a light gray background, dark text, and a blue accent. It sets only a few message colors (others fall back to foreground).

{
  "name": "Minimal Example",
  "description": "A simple light theme with blue accents.",
  "author": "You",
  "version": "1.0",
  "operatorBadge": "shield.checkered",
  "awayBadge": "moon.fill",
  "secureBadge": "lock.fill",
  "botBadge": "cpu",
  "blockedBadge": "hand.raised.fill",
  "light": {
    "ui": {
      "background": { "hex": "#FFFFFF" },
      "foreground": { "hex": "#000000" },
      "accent": { "hex": "#0A84FF" },
      "divider": { "hex": "#D1D1D6" },
      "topicBarBackground": { "hex": "#F2F2F7" },
      "topicBarForeground": { "hex": "#000000" },
      "badgeBackground": { "hex": "#D6E8FF" },
      "badgeForeground": { "hex": "#0A84FF" },
      "sidebarSelectionBackground": { "hex": "#D6E8FF" },
      "sidebarSelectionForeground": { "hex": "#0A84FF" },
      "highlightRowBackground": { "hex": "#FF000026" },
      "expandedGroupRowBackground": { "hex": "#0A84FF14" },
      "operatorBadgeColor": { "hex": "#6B7280" },
      "awayBadgeColor": { "hex": "#6B7280" },
      "secureBadgeColor": { "hex": "#34C759" },
      "botBadgeColor": { "hex": "#6B7280" },
      "blockedBadgeColor": { "hex": "#FF3B30" },
      "scriptEditorHighlightrTheme": "github",
      "joinBadge": "arrow.right.circle.fill",
      "partBadge": "arrow.left.circle.fill",
      "quitBadge": "arrow.left.circle.fill",
      "actionBadge": "star.fill",
      "noticeBadge": "exclamationmark.bubble.fill",
      "infoBadge": "info.circle.fill",
      "topicBadge": "text.quote"
    },
    "messageColors": {
      "categories": {
        "normal": { "hex": "#000000" },
        "error": { "hex": "#FF3B30" }
      },
      "tags": {
        "do": { "hex": "#000000" }
      }
    }
  },
  "dark": {
    "ui": {
      "background": { "hex": "#000000" },
      "foreground": { "hex": "#FFFFFF" },
      "accent": { "hex": "#0A84FF" },
      "divider": { "hex": "#3C3C43" },
      "topicBarBackground": { "hex": "#1C1C1E" },
      "topicBarForeground": { "hex": "#FFFFFF" },
      "badgeBackground": { "hex": "#1B2A40" },
      "badgeForeground": { "hex": "#0A84FF" },
      "sidebarSelectionBackground": { "hex": "#1B2A40" },
      "sidebarSelectionForeground": { "hex": "#0A84FF" },
      "highlightRowBackground": { "hex": "#FF000026" },
      "expandedGroupRowBackground": { "hex": "#0A84FF14" },
      "operatorBadgeColor": { "hex": "#9CA3AF" },
      "awayBadgeColor": { "hex": "#9CA3AF" },
      "secureBadgeColor": { "hex": "#30D158" },
      "botBadgeColor": { "hex": "#9CA3AF" },
      "blockedBadgeColor": { "hex": "#FF453A" },
      "scriptEditorHighlightrTheme": "github-dark",
      "joinBadge": "arrow.right.circle.fill",
      "partBadge": "arrow.left.circle.fill",
      "quitBadge": "arrow.left.circle.fill",
      "actionBadge": "star.fill",
      "noticeBadge": "exclamationmark.bubble.fill",
      "infoBadge": "info.circle.fill",
      "topicBadge": "text.quote"
    },
    "messageColors": {
      "categories": {
        "normal": { "hex": "#FFFFFF" },
        "error": { "hex": "#FF453A" }
      },
      "tags": {
        "notice": { "hex": "#FF6B81" },
        "ctcp": { "hex": "#FF453A" }
      }
    }
  }
}

Troubleshooting

Badge Icons

Themes can customize the SF Symbol icons used for status badges in the nicklist and user info tooltips:

These badges appear next to nicknames in the nicklist and in the user info tooltip when hovering over a nickname.

Example badge customization:

{
  "name": "Custom Badges",
  "operatorBadge": "star.fill",
  "awayBadge": "zzz",
  "secureBadge": "lock.shield.fill",
  "botBadge": "cpu",
  "blockedBadge": "hand.raised.fill",
  "light": { ... },
  "dark": { ... }
}

Common SF Symbol alternatives:

If not specified, the default icons are used. The badge icons apply to both light and dark variants.

Message Badge Symbols

Themes can customize the SF Symbol icons used for message type badges in the sender column. These badges appear instead of nicknames for non-PRIVMSG messages (joins, parts, quits, actions, notices, info, and topic messages).

The following optional fields can be set in the ui object of each palette (light/dark):

Example message badge customization:

{
  "name": "Custom Message Badges",
  "light": {
    "ui": {
      "joinBadge": "arrow.right.circle.fill",
      "partBadge": "arrow.left.circle.fill",
      "quitBadge": "arrow.left.circle.fill",
      "actionBadge": "star.fill",
      "noticeBadge": "exclamationmark.bubble.fill",
      "infoBadge": "info.circle.fill",
      "topicBadge": "text.quote",
      ...
    },
    ...
  },
  "dark": {
    "ui": {
      "joinBadge": "arrow.right.circle.fill",
      "partBadge": "arrow.left.circle.fill",
      "quitBadge": "arrow.left.circle.fill",
      "actionBadge": "star.fill",
      "noticeBadge": "exclamationmark.bubble.fill",
      "infoBadge": "info.circle.fill",
      "topicBadge": "text.quote",
      ...
    },
    ...
  }
}

Common SF Symbol alternatives:

If not specified, the default icons are used. Badge symbols can be customized independently for light and dark variants.

Font Configuration

Themes can specify fonts for message content to customize the appearance and enable IRC script alignment compatibility. The font configuration applies consistently throughout the app to all message content (messages, topics, raw buffer, notices).

Font Structure

The optional fonts object can be added to any ThemePalette (light or dark variant):

{
  "fonts": {
    "messageFontName": "SF Mono",
    "messageFontDesign": "monospaced",
    "messageFontSize": "body"
  }
}

Font Fields

Font Resolution

  1. If messageFontName is specified and the font exists on the system, use that custom font
  2. Otherwise, use system font with the specified messageFontDesign
  3. If no font configuration is provided, default to system body font (.body)

Examples

Monospace font for IRC alignment:

{
  "light": {
    "fonts": {
      "messageFontDesign": "monospaced"
    },
    ...
  },
  "dark": {
    "fonts": {
      "messageFontDesign": "monospaced"
    },
    ...
  }
}

Custom font with specific design:

{
  "light": {
    "fonts": {
      "messageFontName": "SF Mono",
      "messageFontSize": "body"
    },
    ...
  },
  "dark": {
    "fonts": {
      "messageFontName": "SF Mono",
      "messageFontSize": "body"
    },
    ...
  }
}

Design-only (no custom font name):

{
  "light": {
    "fonts": {
      "messageFontDesign": "monospaced",
      "messageFontSize": "small"
    },
    ...
  }
}

Notes

Script Editor Syntax Highlighting

Themes can customize the syntax highlighting theme used in the script editor (TCL script editor). This uses the Highlightr library, which supports many popular code editor themes.

Configuration

The optional scriptEditorHighlightrTheme field can be set in the ui object of each palette (light/dark):

{
  "light": {
    "ui": {
      "scriptEditorHighlightrTheme": "github",
      ...
    },
    ...
  },
  "dark": {
    "ui": {
      "scriptEditorHighlightrTheme": "github-dark",
      ...
    },
    ...
  }
}

Available Highlightr Themes

Common themes available in Highlightr include:

Light themes:

Dark themes:

The complete list of available themes depends on the Highlightr library version. Common themes that work well with TCL syntax include:

Examples

Using different themes for light and dark:

{
  "light": {
    "ui": {
      "scriptEditorHighlightrTheme": "github",
      ...
    }
  },
  "dark": {
    "ui": {
      "scriptEditorHighlightrTheme": "monokai",
      ...
    }
  }
}

Using the same theme for both:

{
  "light": {
    "ui": {
      "scriptEditorHighlightrTheme": "xcode",
      ...
    }
  },
  "dark": {
    "ui": {
      "scriptEditorHighlightrTheme": "xcode-dark",
      ...
    }
  }
}

Notes

Versioning