12

How could I make an interactive function to toggle in between two color themes?

From what I have been able to find there is no variable set to what color theme is currently in use, in fact several can be loaded simultaneously?

Also, in order to toggle you would need to first do disable-theme for the currently loaded theme, to not make themes clash.

How to do that without knowing which theme is currently loaded?

3
  • 3
    (car custom-enabled-themes) returns the currently enabled theme.
    – mutbuerger
    Jun 21, 2016 at 12:08
  • Possible duplicate of How to reset color theme?
    – mutbuerger
    Jun 21, 2016 at 12:15
  • Just use counsel-load-theme, if you use counsel.
    – InHarmsWay
    Mar 31, 2019 at 13:28

9 Answers 9

6

I have made it but I switch 3 themes (My own kosmos, leuven and default)

You can check https://github.com/habamax/.emacs.d/blob/master/lisp/haba-appearance.el

excerpt from it:

(defvar *haba-theme-dark* 'kosmos)
(defvar *haba-theme-light* 'leuven)
(defvar *haba-current-theme* *haba-theme-dark*)

;; disable other themes before loading new one
(defadvice load-theme (before theme-dont-propagate activate)
  "Disable theme before loading new one."
  (mapcar #'disable-theme custom-enabled-themes))


(defun haba/next-theme (theme)
  (if (eq theme 'default)
      (disable-theme *haba-current-theme*)
    (progn
      (load-theme theme t)))
  (setq *haba-current-theme* theme))

(defun haba/toggle-theme ()
  (interactive)
  (cond ((eq *haba-current-theme* *haba-theme-dark*) (haba/next-theme *haba-theme-light*))
        ((eq *haba-current-theme* *haba-theme-light*) (haba/next-theme 'default))
        ((eq *haba-current-theme* 'default) (haba/next-theme *haba-theme-dark*))))

Then bind some key to haba/toggle-theme.

I use emacs on 2 different machines and environments (day, late evening) so there is facility to save/restore current-theme on quit/load emacs. Which is handy :)

1
  • The solution works but it generates the following message at each emacs startup : Warning (bytecomp): ‘mapcar’ called for effect; use ‘mapc’ or ‘dolist’ instead. When replacing mapcar by mapc the message disappears. Consider editing.
    – crocefisso
    Dec 1, 2021 at 15:53
7

I wrote a few functions to cycle through a group of themes.

(setq ivan/themes '(elixir elixir-dark))
(setq ivan/themes-index 0)

(defun ivan/cycle-theme ()
  (interactive)
  (setq ivan/themes-index (% (1+ ivan/themes-index) (length ivan/themes)))
  (ivan/load-indexed-theme))

(defun ivan/load-indexed-theme ()
  (ivan/try-load-theme (nth ivan/themes-index ivan/themes)))

(defun ivan/try-load-theme (theme)
  (if (ignore-errors (load-theme theme :no-confirm))
      (mapcar #'disable-theme (remove theme custom-enabled-themes))
    (message "Unable to find theme file for ‘%s’" theme)))

I call ivan/load-indexed-theme in my init file to initialize my theme.

I bind ivan/cycle-theme to Space\ in evil mode. (Space is my leader key.)

6

Even though the existing answers work fine, I would like to share a simpler one:

(defun toggle-theme ()
  (interactive)
  (if (eq (car custom-enabled-themes) 'leuven)
      (disable-theme 'leuven)
    (enable-theme 'leuven)))
(global-set-key [f5] 'toggle-theme)

This does not disable the default custom theme first, but I like that.

3
  • This is so money! Simple, clean, effective.
    – mcp
    Sep 17, 2020 at 1:18
  • One thing I noticed is if I have no theme enabled in my init, in other words I'm using the default theme, this does not work and reports "custom theme not found".
    – mcp
    Sep 18, 2020 at 16:37
  • 1
    Changing enable-theme to load-theme solved this problem.
    – mcp
    Sep 22, 2020 at 20:10
2

New Emacsen have customize-themes function made to support exactly this, to switch the themes easily.

1

This is the module I wrote for my own .emacs to solve this problem. My basic approach seems to be similar in intent to Maxim Kim's solution (rotating through a list of themes), but I think mine is more modular and may therefore be more accessible to an outsider. On the other hand, I don't have any of Kim's persistence features.

Here is the relevant code, leaving off variable declarations and package commentary:

(require 'dash)

(defun multitheme--enable (theme)
  "As `enable-theme', but load the theme if necessary.
Respect `custom-safe-themes'."
  (if (custom-theme-p theme)
      (enable-theme theme)
    (load-theme theme)))

(defun multitheme-cycle ()
  "Cycle between the themes in `multitheme-base-theme-list'.
If none of these themes is currently active, instead enable the
first element of `multitheme-base-theme-list'.

Also re-enable `multitheme-overtheme' so it remains \"on top\" of
the base theme.

If a theme to be enabled is not yet defined, attempt to load it
first (using `load-theme').  Respect `custom-safe-themes'.

After all theme changes have been made, run
`multitheme-base-change-hook'."
  (interactive)
  (when (require 'validate nil :noerror)
    (validate-variable 'multitheme-base-theme-list)
    (validate-variable 'multitheme-overtheme)
    (validate-variable 'multitheme-base-theme-change-hook))
  (let ((themes (-drop-while
                 (lambda (thm) (not (custom-theme-enabled-p thm)))
                 multitheme-base-theme-list)))
    ;; Cycle base theme
    (if (null themes)
        (multitheme--enable (car multitheme-base-theme-list))
      (disable-theme (car themes))
      (multitheme--enable (or (cadr themes)
                              (car multitheme-base-theme-list))))
    ;; Reassert overtheme
    (when multitheme-overtheme
      (multitheme--enable multitheme-overtheme))
    ;; Run hooks
    (run-hooks 'multitheme-base-theme-change-hook)))
1

I know I'm a little late to the party but I created a package to do exactly that, and much more.

It basically lets you define a list of your favorite color themes (which is optional) and move through the list conveniently.

3
  • What does this do that narendraj9's answer does not do
    – mmmmmm
    Oct 13, 2020 at 17:13
  • It started with a few convenience functions to help you set a set of favorite themes and switch through them on demand but then later has been added with ivy integration and more. It also provides other features like a hook when a theme is switched to using theme-looper and a few more.
    – myTerminal
    Oct 13, 2020 at 18:14
  • A fresh testimonial :) github.com/myTerminal/theme-looper/issues/14#issue-737899225
    – myTerminal
    Nov 6, 2020 at 17:52
0

I have this setup for cycling themes:

(defvar quick-switch-themes
  (let ((themes-list (list 'jazz
                           ;; 'vicarie-and-blackboard
                           ;; 'tangotango
                           'poet)))
    (nconc themes-list themes-list))
  "A circular list of themes to keep switching between.
Make sure that the currently enabled theme is at the head of this
list always.

A nil value implies no custom theme should be enabled.")

(defun quick-switch-themes* ()
  "Switch between to commonly used faces in Emacs.
One for writing code and the other for reading articles."
  (interactive)
  (if-let* ((next-theme (cadr quick-switch-themes)))
      (progn (when-let* ((current-theme (car quick-switch-themes)))
               (disable-theme (car quick-switch-themes)))
             (load-theme next-theme t)
             (message "Loaded theme: %s" next-theme))
    ;; Always have the dark mode-line theme
    (mapc #'disable-theme (delq 'smart-mode-line-dark custom-enabled-themes)))
  (setq quick-switch-themes (cdr quick-switch-themes)))
0

I was happy with none of the approaches, as I wanted a noninteractive solution that could be used quickly and also from the CLI, so I wrote this:

(defun load-theme-string (theme)
  (if (-contains? (custom-available-themes) (intern theme)) (load-theme (intern theme))))
(defun toggle-theme (&optional suffix)
  "Heuristically toggle between light and dark themes."
  (interactive)
  (let* ((theme (s-replace-all '(("light" . "dark") ("dark" . "light")
                     ("black" . "white") ("white" . "black")
                     ("day" . "night") ("night" . "day"))
                   (symbol-name doom-theme)))
        (theme-base (s-replace-regexp "-[^-]*$" "" theme)))
    (or (if suffix (or (load-theme-string (concat theme "-" suffix)) (load-theme-string (concat theme-base "-" suffix)))) (load-theme-string (if (and (not suffix) (equal theme (symbol-name doom-theme))) (concat theme "-light") theme)) (load-theme-string theme-base))
    )
  )

It is based on Doom and intentionally does not handle every edge case, but works for most themes included in Doom Emacs - though it can of course be adjusted for other environments

Futhermore, it can be called interactively or with an argument, so I can switch the theme from the terminal (providing "light"/"dark" or no parameter):

emacsclient -e "(toggle-theme \"dark\")"
0

The answer michel-de-ruiter posted in 2019 looked clean, but on my end it wouldn't revert to the initial theme after toggling to the second, so here is my iteration on it with two example themes:

(defun toggle-theme ()
  (interactive)
  (cond ((eq (car custom-enabled-themes) 'doom-palenight)
          (mapc #'disable-theme custom-enabled-themes)
          (load-theme 'doom-nord-light))
        ((eq (car custom-enabled-themes) 'doom-nord-light)
          (mapc #'disable-theme custom-enabled-themes)
          (load-theme 'doom-palenight))))
(global-set-key (kbd "C-c t") 'toggle-theme)

It can be expanded to cycle among more than two themes, see example below for three themes:

(defun toggle-theme ()
  (interactive)
  (cond ((eq (car custom-enabled-themes) 'doom-palenight)
          (mapc #'disable-theme custom-enabled-themes)
          (load-theme 'doom-nord-light))
        ((eq (car custom-enabled-themes) 'doom-nord-light)
     (mapc #'disable-theme custom-enabled-themes)
     (load-theme 'doom-Iosvkem))
        ((eq (car custom-enabled-themes) 'doom-Iosvkem)
          (mapc #'disable-theme custom-enabled-themes)
          (load-theme 'doom-palenight))))
(global-set-key (kbd "C-c t") 'toggle-theme)

The line disabling the current theme can perhaps be removed to make the function shorter, but I heard that sometimes multiple themes can partly blend if applied before

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.