Kindness City Blog
17 Jan 2026

Picking an emacs colour theme

TL;DR:

Here're a couple of handy functions to quickly try out all the dark-mode / light-mode themes you've already got installed, and pick the one that feels right for now.

There are too many choices

I know that emacs can be as pretty as I want it to be, but when I run M-x load-theme TAB I just see a big list of more-or-less meaningless names:

Type M-RET on a completion to select it.
Type M-<down> or M-<up> to move point between completions.

23 possible completions:
adwaita                 deeper-blue
dichromacy              leuven
leuven-dark             light-blue
manoj-dark              misterioso
modus-operandi          modus-operandi-deuteranopia
modus-operandi-tinted   modus-operandi-tritanopia
modus-vivendi           modus-vivendi-deuteranopia
modus-vivendi-tinted    modus-vivendi-tritanopia
tango                   tango-dark
tsdh-dark               tsdh-light
wheatgrass              whiteboard
wombat

They're not even organised by light-mode and dark-mode!

The UX of https://emacsthemes.org is better, because we get thumbnails of what all the themes look like. But there are way too many themes on there, and I often just want to pick the best of what I've got on my local machine. Whatever came with the default install.

M-x customize-themes is a bit better in that it gives me a text description of each theme. But it's not very visual:

Available Custom Themes:
[ ][ adwaita] -- Face colors similar to the default theme of Gnome 3 (Adwaita).
[ ][ deeper-blue] -- Face colors using a deep blue background.
[ ][ dichromacy] -- Face colors suitable for red/green color-blind users.
...
[ ][ tsdh-light] -- A light Emacs theme.
[ ][ wheatgrass] -- High-contrast green/blue/brown faces on a black background.
[ ][ whiteboard] -- Face colors similar to markers on a whiteboard.
[ ][ wombat] -- Medium-contrast faces with a dark gray background.

What I want

I want to make my theme decision in two parts:

  1. Light or dark?
  2. Which one looks nicest right now, for the stuff I'm working on?

So, if I'm in a dark room, I think I want a function to cycle through all the dark-themes I have installed, and just try them one after another.

(defun gds-try-next-dark-theme ()
  "Try the next available dark theme.

If we're currently using one or more themes, disable them first."
  (interactive) ... )

…and if I'm in a bright room, I'll want the light-mode equivalent:

(defun gds-try-next-light-theme ()
  "Try the next available light theme.

If we're currently using one or more themes, disable them first."
  (interactive) ... )

I'm going to do this by manually categorising the themes that I happen to have on my computer right now into two lists: one for dark themes, and the other for light.

Then I'm going to write the functions I actually want: for cycling through themes.

Finally, I want to be able to maintain the thing. So if a new theme appears in a new version of emacs, I'll want some way of detecting it:

(defun gds-check-for-new-themes ()
  "Check for uncategorised themes.

Print and return a list of themes that are available to use with
`load-theme', but which are not yet categorised in either of
`gds-builtin-light-color-themes' or `gds-builtin-dark-color-themes'."
  (interactive) ... )

How to get it…

This elisp should do the trick:

(defvar gds-builtin-light-color-themes
  [ adwaita
    dichromacy
    leuven
    modus-operandi
    modus-operandi-deuteranopia
    modus-operandi-tinted
    modus-operandi-tritanopia
    modus-vivendi
    tango
    tsdh-light
    whiteboard ]
  "A manually-curated array of light-mode color themes. You can cycle
through them with `gds-try-next-light-theme'.

See also `gds-next-light-theme-index' and `gds-try-next-dark-theme'.")

(defvar gds-next-light-theme-index 0
  "The index of the next light-mode color theme to try if you call
`gds-try-next-light-theme'.

See also `gds-builtin-light-color-themes' and `gds-try-next-dark-theme'.")

(defvar gds-builtin-dark-color-themes
  [ deeper-blue
    leuven-dark
    manoj-dark
    misterioso
    modus-vivendi-deuteranopia
    modus-vivendi-tinted
    modus-vivendi-tritanopia
    tango-dark
    tsdh-dark
    wheatgrass
    wombat]
  "A manually-curated array of dark-mode color themes. You can cycle
through them with `gds-try-next-dark-theme'.

See also `gds-next-dark-theme-index' and `gds-try-next-light-theme'.")

(defvar gds-next-dark-theme-index 0
    "The index of the next light-mode color theme to try if you call
`gds-try-next-dark-theme'.

See also `gds-builtin-dark-color-themes' and `gds-try-next-light-theme'.")

(defun gds-try-next-light-theme ()
  "Try the next available light theme.

If we're currently using one or more themes, disable them first.

See also `gds-try-next-dark-theme' and `gds-check-for-new-themes'."
  (interactive)
  (seq-do 'disable-theme custom-enabled-themes)
  (load-theme (aref gds-builtin-light-color-themes gds-next-light-theme-index))
  (setq gds-next-light-theme-index
        (gds-get-rotated-index-of gds-next-light-theme-index
                                  gds-builtin-light-color-themes)))

(defun gds-try-next-dark-theme ()
  "Try the next available dark theme.

If we're currently using one or more themes, disable them first.

See also `gds-try-next-light-theme' and `gds-check-for-new-themes'."
  (interactive)
  (seq-do 'disable-theme custom-enabled-themes)
  (load-theme (aref gds-builtin-dark-color-themes gds-next-dark-theme-index))
  (setq gds-next-dark-theme-index
        (gds-get-rotated-index-of gds-next-dark-theme-index
                                  gds-builtin-dark-color-themes)))

(defun gds-get-rotated-index-of (idx arr)
  "Rotate the index IDX into array ARR.

ARR must be an array. IDX must be an int that indexes into that
array. We return the index of the next element of the array."
  (% (+ 1 idx) (length arr)))

(defun gds-check-for-new-themes ()
  "Check for uncategorised themes.

Print and return a list of themes that are available to use with
`load-theme', but which are not yet categorised in either of
`gds-builtin-light-color-themes' or `gds-builtin-dark-color-themes'."
  (interactive)
  (let ((new-themes (seq-filter 'gds-is-uncategorised-theme-p
                            (custom-available-themes))))
    (if new-themes
        (message "Detected new themes: %S" new-themes)
      (message "There are no new themes."))
    new-themes))

(defun gds-is-uncategorised-theme-p (theme)
  "Check if we've yet to categorise THEME.

Return t if we don't recognise THEME.

Return nil if THEME is in our list of light or dark themes. Those are our
main categories that we can rotate through with
`gds-try-next-light-theme' and `gds-try-next-dark-theme'

Return nil if THEME is `light-blue'. Because that one's apparently
obsolete since Emacs 29.1, according to the warnings it prints."
  (not (or (eq theme 'light-blue)
           (seq-contains-p gds-builtin-light-color-themes theme)
           (seq-contains-p gds-builtin-dark-color-themes theme))))
Tags: sysadmin emacs UX

There's no comments mechanism in this blog (yet?), but I welcome emails and fedi posts. If you choose to email me, you'll have to remove the .com from the end of my email address by hand.

You can also follow this blog with RSS.

Other posts