Understanding Emacs Themes and Faces
Emacs is not just a text editor—it's a fully customizable environment where every pixel, color, and font can be tailored to your preferences. At the heart of this visual customization system lie two interconnected concepts: themes and faces.
A face in Emacs is a named collection of visual attributes—foreground color, background color, font family, weight, slant, underline, and more—that controls how a specific piece of text is displayed. Every element in Emacs, from the cursor to the mode line to syntax-highlighted keywords, is rendered through a face. A theme is essentially a bundled collection of face definitions that can be loaded, unloaded, and switched between seamlessly, transforming the entire appearance of your editor in a single command.
This dual-layer architecture gives you extraordinary power: you can either adopt a complete visual package via a theme, or surgically adjust individual faces to create a look that is entirely your own.
Why Customizing Emacs Appearance Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Investing time in theming and customization yields tangible benefits that compound over the thousands of hours you'll spend in your editor:
- Reduced eye strain: Carefully chosen contrast ratios and background colors directly impact fatigue during long coding sessions. A light theme that's too bright or a dark theme with insufficient contrast can cause squinting and headaches.
- Improved code comprehension: Semantic coloring—where different syntax categories have distinct, intuitive hues—helps your brain parse code structure faster. Well-designed themes make bugs stand out and logic flow become visually apparent.
- Personal workflow alignment: Your editor's appearance should match your working environment. A dark theme for late-night coding, a light theme for bright offices, or a high-contrast theme for presentations.
- Ownership and motivation: A beautiful, personalized editor makes the act of writing code feel more enjoyable. It transforms Emacs from a tool into your tool.
- Accessibility: For users with visual impairments, customizing faces isn't just aesthetic—it's essential for readability and productivity.
Exploring Built-in Themes
Modern Emacs (version 24 and above) ships with a curated selection of themes that you can try immediately without any external packages. To browse the available built-in themes:
;; List all available themes on your system
M-x customize-themes RET
This opens the Custom Themes browser, showing checkboxes for each installed theme. You can toggle them on and off interactively to preview combinations. Alternatively, load a theme programmatically:
;; Load a built-in theme instantly
(load-theme 'tango-dark t) ; t means "no confirmation prompt"
;; Some popular built-in themes you can try:
;; - tango-dark, tango-light
;; - wombat
;; - adwaita
;; - deeper-blue
;; - dichromacy (for color-blind users)
;; - leuven
;; - misterioso
;; - tsdh-dark, tsdh-light
;; - whiteboard
;; - manoj-dark
To see which theme is currently active and get a description of each face it defines:
;; Describe the currently active theme
M-x describe-theme RET
Enabling Multiple Themes
Emacs allows you to layer themes. When you load multiple themes, faces defined in later-loaded themes override earlier ones. This is useful for creating hybrid looks:
;; Load a base theme, then layer a secondary theme on top
(load-theme 'tango-dark t)
(load-theme 'dichromacy t) ; applies color-blind friendly adjustments on top
To unload a specific theme without affecting others:
;; Remove only one theme from the layered stack
(disable-theme 'dichromacy)
Installing Third-Party Themes
The true breadth of Emacs theming emerges when you tap into the community ecosystem. Hundreds of meticulously crafted themes are available through package archives like MELPA.
Step 1: Configure Package Archives
Ensure your Emacs can reach MELPA (the most comprehensive package repository):
;; Add to your init.el or .emacs
(require 'package)
;; Define archive sources
(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("melpa" . "https://melpa.org/packages/")))
;; Initialize package system
(package-initialize)
(package-refresh-contents)
Step 2: Discover and Install Themes
You can browse themes directly from Emacs:
;; List packages matching "theme" in their name
M-x package-list-packages RET
;; Then press / and type: theme
Or install a known theme programmatically:
;; Install a theme package (only once)
(unless (package-installed-p 'doom-themes)
(package-install 'doom-themes))
;; Install several popular theme packages
(unless (package-installed-p 'modus-themes)
(package-install 'modus-themes))
(unless (package-installed-p 'dracula-theme)
(package-install 'dracula-theme))
(unless (package-installed-p 'gruvbox-theme)
(package-install 'gruvbox-theme))
(unless (package-installed-p 'spacemacs-theme)
(package-install 'spacemacs-theme))
(unless (package-installed-p 'nord-theme)
(package-install 'nord-theme))
Step 3: Load the Installed Theme
;; Load a third-party theme
(load-theme 'doom-one t)
;; Or conditionally load based on time of day
(let ((hour (string-to-number (format-time-string "%H"))))
(if (and (> hour 6) (< hour 18))
(load-theme 'modus-operandi t) ; light theme for daytime
(load-theme 'modus-vivendi t))) ; dark theme for nighttime
Notable Third-Party Theme Packages
- doom-themes — A large collection of polished themes with tight integration for doom-modeline, neotree, and org-mode. Includes doom-one (dark), doom-vibrant, doom-nord, and more.
- modus-themes — Developed by Protesilaos Stavrou, these are built for high accessibility and WCAG compliance. Now part of Emacs core as of Emacs 28.
- dracula-theme — A dark purple-themed aesthetic popularized across many editors.
- gruvbox-theme — Retro earthy tones, both dark and light variants, excellent for long sessions.
- nord-theme — Cool bluish palette inspired by Arctic landscapes.
- spacemacs-theme — Elegant dark and light themes from the Spacemacs project.
- catppuccin-theme — Soft pastel themes with multiple flavor variants.
- ef-themes — Another accessibility-focused collection by Protesilaos, with higher contrast than Modus.
Understanding Faces in Depth
A face is a symbol that holds a property list of visual attributes. You can inspect any face interactively:
;; Open the face description for a specific face
M-x describe-face RET font-lock-keyword-face RET
The output shows something like:
Face: font-lock-keyword-face
Defined in: font-lock.el
Attributes:
:foreground "#a020f0"
:weight bold
:slant unspecified
:underline unspecified
:height unspecified
Every attribute that is unspecified inherits from a parent face or the default face. This cascading inheritance is powerful: by tweaking just the default face, you can affect the entire editor's baseline appearance.
Key Face Attributes
- :foreground — Text color (hex string like "#ff0000" or color name like "red")
- :background — Background fill color behind the text
- :weight — Font weight:
bold,normal,light,thin - :slant —
italic,oblique,normal - :underline —
t,nil, or a color for colored underlines - :overline — Line above the text
- :strike-through — Strikethrough text
- :height — Font height (relative float like 1.2 or absolute integer)
- :family — Font family name (e.g., "Fira Code", "JetBrains Mono")
- :box — Draw a box around the text; can be
tor a color - :inverse-video — Swap foreground and background colors
- :extend — Whether the face's background extends to the margin
Customizing Individual Faces
You don't need to create an entire theme to make targeted improvements. Emacs provides multiple pathways for face customization, from interactive UI to programmatic Lisp.
Method 1: The Customize Interface (Interactive)
;; Open the face customizer for a specific face
M-x customize-face RET default RET
;; Browse all faces grouped by category
M-x list-faces-display RET
In the customization buffer, you can change attributes, see a live preview, and save changes to your init file. When you save, Emacs writes the settings using the custom-set-faces function:
;; What Emacs writes to your init file when you save via customize
(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, the magic will go away.
;; You must restart Emacs or eval this block.
'(default ((t (:family "Fira Code" :height 140 :weight normal))))
'(font-lock-comment-face ((t (:slant italic :foreground "#8b8b8b")))))
Method 2: Programmatic Face Customization
For full control, use set-face-attribute directly in your init file:
;; Set the default face — this affects everything that inherits from it
(set-face-attribute 'default nil
:family "JetBrains Mono"
:height 130
:weight 'normal
:foreground "#e0e0e0"
:background "#1e1e1e")
;; Customize syntax highlighting faces
(set-face-attribute 'font-lock-keyword-face nil
:foreground "#569cd6"
:weight 'bold)
(set-face-attribute 'font-lock-string-face nil
:foreground "#ce9178"
:slant 'italic)
(set-face-attribute 'font-lock-comment-face nil
:foreground "#6a9955"
:slant 'italic)
(set-face-attribute 'font-lock-function-name-face nil
:foreground "#dcdcaa")
(set-face-attribute 'font-lock-variable-name-face nil
:foreground "#9cdcfe")
(set-face-attribute 'font-lock-type-face nil
:foreground "#4ec9b0")
(set-face-attribute 'font-lock-constant-face nil
:foreground "#4fc1ff")
(set-face-attribute 'font-lock-warning-face nil
:foreground "#f44747"
:weight 'bold)
Method 3: Using the Custom Theme Format
For sharing your configurations or applying them as a cohesive set, use custom-theme-set-faces:
;; Define a set of face overrides in theme format
(custom-theme-set-faces 'user-theme
'(default ((t (:background "#282c34" :foreground "#abb2bf"))))
'(cursor ((t (:background "#528bff"))))
'(region ((t (:background "#3e4451"))))
'(mode-line ((t (:background "#383b42" :foreground "#979797" :box nil))))
'(mode-line-inactive ((t (:background "#282c34" :foreground "#5c6370" :box nil))))
'(line-number ((t (:foreground "#4b5263"))))
'(line-number-current-line ((t (:foreground "#abb2bf" :weight bold))))
'(header-line ((t (:background "#282c34" :foreground "#98be65"))))
'(fringe ((t (:background "#282c34"))))
'(hl-line ((t (:background "#2c323b"))))
'(link ((t (:foreground "#61afef" :underline t))))
'(link-visited ((t (:foreground "#c678dd" :underline t))))
'(highlight ((t (:background "#3a3f4b")))))
Customizing the Mode Line
The mode line is one of the most visible elements of Emacs, yet it's often left in its default state. A well-designed mode line provides information at a glance without being distracting.
Understanding Mode Line Faces
The mode line uses several specialized faces:
- mode-line — The active window's mode line
- mode-line-inactive — Mode lines in non-selected windows
- mode-line-highlight — Mouse hover effects on mode line elements
- mode-line-buffer-id — The buffer name display
- mode-line-emphasis — Emphasis markers in the mode line
Basic Mode Line Styling
;; Give the active mode line a distinctive look
(set-face-attribute 'mode-line nil
:background "#383b42"
:foreground "#979797"
:box '(:line-width 1 :color "#4a4f59")
:height 0.9)
;; Make inactive mode lines subtle and unobtrusive
(set-face-attribute 'mode-line-inactive nil
:background "#282c34"
:foreground "#5c6370"
:box '(:line-width 1 :color "#282c34")
:height 0.9)
;; Highlight buffer name in the mode line
(set-face-attribute 'mode-line-buffer-id nil
:foreground "#61afef"
:weight 'bold)
Third-Party Mode Line Packages
Several packages replace the mode line entirely with richer, more configurable alternatives:
;; doom-modeline — modern, information-rich mode line
(unless (package-installed-p 'doom-modeline)
(package-install 'doom-modeline))
(doom-modeline-mode 1)
;; moody — minimal, elegant mode line with vertical tabs feel
(unless (package-installed-p 'moody)
(package-install 'moody))
(moody-mode 1)
;; mini-modeline — shrink the mode line to a single tiny line
(unless (package-installed-p 'mini-modeline)
(package-install 'mini-modeline))
(mini-modeline-mode 1)
Font Configuration for Coding
Typography is a cornerstone of a comfortable editing environment. Emacs handles fonts through the default face and can use different fonts for different purposes.
Setting the Main Coding Font
;; Set a monospaced coding font with ligature support
(set-face-attribute 'default nil
:family "Fira Code"
:weight 'light
:height 135)
;; Alternative: Use a different font for specific modes via hooks
(defun my-set-prose-font ()
"Switch to a variable-pitch font for prose modes."
(set-face-attribute 'default nil :family "Iosevka Etoile" :height 150))
(add-hook 'org-mode-hook 'my-set-prose-font)
(add-hook 'markdown-mode-hook 'my-set-prose-font)
Working with Variable-Pitch Fonts in Org Mode
Org mode supports mixing monospaced and variable-pitch fonts beautifully. Set the base face to a variable-pitch font for prose, while keeping code blocks monospaced:
;; In org-mode, use variable-pitch for body text
(add-hook 'org-mode-hook
(lambda ()
(set-face-attribute 'default nil :family "ET Book" :height 160)
;; Ensure code blocks stay monospaced
(set-face-attribute 'org-block nil :family "Fira Code" :height 130)
(set-face-attribute 'org-code nil :family "Fira Code" :height 130)
(set-face-attribute 'org-table nil :family "Fira Code" :height 130)))
Creating Your Own Theme from Scratch
Creating a custom theme is surprisingly straightforward. A theme file is simply a Lisp file that uses deftheme and custom-theme-set-faces (and optionally custom-theme-set-variables).
Anatomy of a Theme File
;;; my-awesome-theme.el — A complete custom Emacs theme
(deftheme my-awesome-theme
"A warm, low-contrast dark theme optimized for long coding sessions.")
;; Define a color palette as variables for consistency
(let ((bg "#1a1b26") ; dark navy background
(bg-alt "#24283b") ; slightly lighter background
(fg "#c0caf5") ; soft white foreground
(fg-dim "#565f89") ; dimmed text
(red "#f7768e") ; errors, warnings
(orange "#ff9e64") ; numbers, constants
(yellow "#e0af68") ; strings
(green "#9ece6a") ; success, functions
(teal "#1abc9c") ; types
(blue "#7aa2f7") ; keywords
(purple "#ad8ee6") ; special symbols
(magenta "#bb9af7") ; preprocessor
(cyan "#7dcfff") ; regexp groups
(comment "#565f89")) ; comments
;; Apply faces using the palette
(custom-theme-set-faces 'my-awesome-theme
;; Core editor faces
`(default ((t (:background ,bg :foreground ,fg))))
`(cursor ((t (:background ,blue :foreground ,bg))))
`(region ((t (:background ,bg-alt :foreground ,fg))))
`(fringe ((t (:background ,bg))))
`(hl-line ((t (:background ,bg-alt :extend t))))
`(line-number ((t (:foreground ,fg-dim :background ,bg))))
`(line-number-current-line ((t (:foreground ,fg :background ,bg-alt :weight bold))))
;; Syntax highlighting
`(font-lock-keyword-face ((t (:foreground ,blue :weight bold))))
`(font-lock-string-face ((t (:foreground ,yellow))))
`(font-lock-comment-face ((t (:foreground ,comment :slant italic))))
`(font-lock-function-name-face ((t (:foreground ,green))))
`(font-lock-variable-name-face ((t (:foreground ,fg))))
`(font-lock-type-face ((t (:foreground ,teal))))
`(font-lock-constant-face ((t (:foreground ,orange))))
`(font-lock-warning-face ((t (:foreground ,red :weight bold))))
`(font-lock-builtin-face ((t (:foreground ,blue))))
`(font-lock-preprocessor-face ((t (:foreground ,magenta))))
`(font-lock-regexp-grouping-backslash ((t (:foreground ,cyan :weight bold))))
`(font-lock-regexp-grouping-construct ((t (:foreground ,cyan))))
;; Search and match
`(match ((t (:background ,blue :foreground ,bg :weight bold))))
`(isearch ((t (:background ,orange :foreground ,bg :weight bold))))
`(isearch-fail ((t (:background ,red :foreground ,bg))))
`(lazy-highlight ((t (:background ,bg-alt :foreground ,orange))))
;; Mode line
`(mode-line ((t (:background ,bg-alt :foreground ,fg :box (:line-width 1 :color ,bg-alt) :height 0.9))))
`(mode-line-inactive ((t (:background ,bg :foreground ,fg-dim :box (:line-width 1 :color ,bg) :height 0.9))))
`(mode-line-buffer-id ((t (:foreground ,blue :weight bold))))
`(mode-line-emphasis ((t (:foreground ,yellow))))
;; UI elements
`(header-line ((t (:background ,bg-alt :foreground ,fg))))
`(vertical-border ((t (:foreground ,bg-alt))))
`(minibuffer-prompt ((t (:foreground ,blue :weight bold))))
`(link ((t (:foreground ,cyan :underline t))))
`(link-visited ((t (:foreground ,purple :underline t))))
`(button ((t (:foreground ,blue :box (:line-width 1 :color ,blue)))))
;; Org mode specific faces
`(org-level-1 ((t (:foreground ,blue :weight bold :height 1.2))))
`(org-level-2 ((t (:foreground ,green :weight bold :height 1.1))))
`(org-level-3 ((t (:foreground ,teal :weight bold))))
`(org-level-4 ((t (:foreground ,purple))))
`(org-level-5 ((t (:foreground ,orange))))
`(org-code ((t (:foreground ,yellow :background ,bg-alt))))
`(org-block ((t (:background ,bg-alt))))
`(org-block-begin-line ((t (:foreground ,comment :background ,bg-alt))))
`(org-block-end-line ((t (:foreground ,comment :background ,bg-alt))))
`(org-table ((t (:foreground ,fg))))
`(org-date ((t (:foreground ,cyan))))
`(org-todo ((t (:foreground ,red :weight bold))))
`(org-done ((t (:foreground ,green :weight bold))))
`(org-agenda-date ((t (:foreground ,blue :weight bold))))
;; Diff faces
`(diff-added ((t (:background "#1b3d2c" :foreground ,green))))
`(diff-removed ((t (:background "#3d1b2c" :foreground ,red))))
`(diff-context ((t (:foreground ,fg-dim))))
`(diff-file-header ((t (:background ,bg-alt :foreground ,fg :weight bold))))
`(diff-hunk-header ((t (:background ,bg-alt :foreground ,purple))))
;; Dired faces
`(dired-directory ((t (:foreground ,blue :weight bold))))
`(dired-symlink ((t (:foreground ,cyan))))
`(dired-marked ((t (:foreground ,orange :weight bold))))
;; Magit faces (if you use Magit)
`(magit-branch ((t (:foreground ,blue :weight bold))))
`(magit-hash ((t (:foreground ,fg-dim))))
`(magit-section-heading ((t (:foreground ,green :weight bold))))
`(magit-diff-added ((t (:background "#1b3d2c" :foreground ,green))))
`(magit-diff-removed ((t (:background "#3d1b2c" :foreground ,red))))))
;;; Provide the theme so it can be loaded with load-theme
(provide-theme 'my-awesome-theme)
(provide 'my-awesome-theme)
;;; my-awesome-theme.el ends here
Installing and Loading Your Custom Theme
Place your theme file in ~/.emacs.d/themes/ (create the directory if needed), then add it to Emacs's search path:
;; Add your personal themes directory to the load path
(add-to-list 'custom-theme-load-path
(expand-file-name "themes" user-emacs-directory))
;; Now you can load your theme like any other
(load-theme 'my-awesome-theme t)
;; Or make it the default and prevent the custom-theme-load-path
;; warning on startup
(setq custom-theme-directory
(expand-file-name "themes" user-emacs-directory))
Dynamic Theme Switching
A powerful customization pattern is to switch themes based on external conditions like time of day, system appearance (dark/light mode), or even project type.
Time-Based Theme Switching
;; Automatically switch theme based on time of day
(defun my-load-time-appropriate-theme ()
"Load a light or dark theme based on current hour."
(let ((hour (string-to-number (format-time-string "%H" t))))
(if (and (>= hour 6) (< hour 18))
(progn
(disable-theme 'modus-vivendi)
(load-theme 'modus-operandi t t))
(progn
(disable-theme 'modus-operandi)
(load-theme 'modus-vivendi t t)))))
;; Run on startup
(my-load-time-appropriate-theme)
;; Optionally, run periodically via a timer for seamless transitions
(run-at-time "07:00" (* 12 3600) 'my-load-time-appropriate-theme)
(run-at-time "18:30" (* 12 3600) 'my-load-time-appropriate-theme)
System Dark Mode Detection (macOS)
;; Detect macOS dark mode and switch Emacs theme accordingly
(defun my-sync-with-macos-dark-mode ()
"Switch Emacs theme to match macOS appearance."
(let ((dark-mode-p
(string-equal
(string-trim
(shell-command-to-string
"defaults read -g AppleInterfaceStyle 2>/dev/null"))
"Dark")))
(if dark-mode-p
(progn
(disable-theme 'modus-operandi)
(load-theme 'modus-vivendi t t))
(progn
(disable-theme 'modus-vivendi)
(load-theme 'modus-operandi t t)))))
;; Check on focus-in (when switching back to Emacs)
(add-hook 'focus-in-hook 'my-sync-with-macos-dark-mode)
Customizing the Cursor and Visual Indicators
Beyond text faces, Emacs offers fine-grained control over cursors, indicators, and UI chrome that affect your editing experience.
;; Cursor customization
(setq cursor-type 'bar) ; vertical bar cursor
;; Alternatives: 'hbar (horizontal), 'box (block), '(bar . 2) (bar of width 2)
;; Or a hollow box: '(hollow . 2)
(setq cursor-in-non-selected-windows 'hollow) ; hollow cursor in inactive windows
(setq x-stretch-cursor t) ; widen cursor to cover character width
;; Blink settings
(blink-cursor-mode 1) ; enable blinking
(setq blink-cursor-interval 0.5) ; blink every 0.5 seconds
(setq blink-cursor-delay 0.2) ; initial delay before blinking
;; Make the cursor color match your theme
(set-face-attribute 'cursor nil :background "#ff6c6c")
;; Highlight the current line
(global-hl-line-mode 1)
(set-face-attribute 'hl-line nil
:background "#2c323b"
:extend t) ; extend background to the window edge
;; Show matching parentheses in a distinct color
(show-paren-mode 1)
(set-face-attribute 'show-paren-match nil
:background "#3a3f4b"
:weight 'bold)
(set-face-attribute 'show-paren-mismatch nil
:background "#ff0000"
:foreground "#ffffff"
:weight 'bold)
;; Customize the region selection appearance
(set-face-attribute 'region nil
:background "#3e4451"
:foreground nil) ; nil = don't change foreground, keep text readable
Working with the Custom System
Emacs's Custom system (custom.el) provides a structured way to manage settings. Understanding its interaction with themes is crucial for maintaining a clean configuration.
How Custom and Themes Interact
Settings saved via M-x customize are stored using custom-set-variables and custom-set-faces. These are separate from themes and persist across theme switches. The priority order is:
- Custom-set-faces (highest priority — saved by Custom interface)
- Loaded themes (in reverse order of loading — last loaded wins)
- Face default values (lowest priority)
This means if you customize a face via the Custom interface and save it, that setting will override any theme you load later. This can be confusing if you switch themes and wonder why certain faces aren't changing.
Resolving Conflicts
;; Check what's overriding a face
M-x customize-face RET default RET
;; Look at the "Theme" and "Custom" lines in the buffer
;; Reset a face to its theme-defined value
(customize-set-variable 'custom-theme-allow-override t)
;; Remove custom face settings entirely (use with caution)
(custom-set-faces) ; empty call clears all custom faces
;; Or surgically reset a single face's custom override
(set-face-attribute 'font-lock-comment-face nil
:foreground 'unspecified) ; revert to theme default
Organizing Your Custom Settings
Many Emacs users prefer to keep Custom settings in a separate file to avoid cluttering their main configuration:
;; In your init.el, direct Custom to a separate file
(setq custom-file (expand-file-name "custom-settings.el" user-emacs-directory))
;; Load the custom settings file if it exists
(if (file-exists-p custom-file)
(load custom-file)
;; Create it if it doesn't exist
(write-region ";; Custom settings managed by Emacs\n" nil custom-file))
Performance Considerations
While theming is primarily visual, some choices can affect Emacs's rendering performance, especially on older hardware or in terminal frames.
- Avoid excessive face definitions: Each face definition adds a small overhead. A theme with thousands of faces is fine on modern hardware, but be mindful in constrained environments.
- Use simple box styles: Complex box shadows and multiple overlays can slow down redisplay. Keep mode-line boxes simple.
- Terminal considerations: In terminal Emacs (
emacs -nw), you're limited to 256 colors (or true color if supported). Not all themes degrade gracefully. Test your theme in terminal mode:
;; Check if your Emacs supports true color in terminal
(if (eq (display-color-cells) 16777216)
(message "True color terminal detected — full theme support available")
(message "Limited color terminal — some theme colors may be approximated"))
- Lazy-load expensive customizations: If you have complex font logic or conditional theming, defer it with
after-init-hookto speed up startup:
;; Defer theme loading to after startup for faster init
(add-hook 'after-init-hook
(lambda ()
(load-theme 'modus-vivendi t)
(run-with-timer 0.1 nil
(lambda ()
(set-face-attribute 'default nil :family "Fira Code" :height 135)))))
Best Practices for Theme Customization
- Start with a well-maintained base theme. Building from scratch is educational, but for daily use, leverage the work of experienced designers. Themes like Modus, Doom, or Gruvbox have been refined over years for readability and consistency.
- Use the cascade, don't fight it. If a theme gets 90% of what you want right, use
custom-theme-set-facesto override just the faces that bother you, rather than creating a whole new theme. - Version-control your face customizations. Treat your
init.elface settings as code. Use Git to track changes so you can experiment fearlessly and roll back when needed. - Test with multiple file types. A theme that looks great in Python may fall apart in Org mode or HTML. Test across your most-used major modes before committing.
- Consider color vision accessibility. Approximately 8