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\")"
(car custom-enabled-themes)returns the currently enabled theme.