How to add dark mode for phoenix 1.7?


I am new to TailwindCSS and try to add a dark mode to a freshly generated phoenix app.

From Dark Mode - Tailwind CSS I learned

  • to add darkMode: 'class' to my tailwind.config.js,
  • to add class="dark" to the html-tag, and
  • to put dark: in front of any color, like so dark:bg-black

That works so far, but the user is not able to switch between dark and normal mode yet. Is there a way to remove/add class="dark" from/to the html-tag on server side in pure elixir with Lieview or has this to be done via JS?

For now, I have added "dark" to the <html> tag by LiveView JS hook

That’s what I’ve been using on my website:

// app.js
function darkExpected() {
  return localStorage.theme === 'dark' || (!('theme' in localStorage) &&
    window.matchMedia('(prefers-color-scheme: dark)').matches);

function initDarkMode() {
  // On page load or when changing themes, best to add inline in `head` to avoid FOUC
  if (darkExpected()) document.documentElement.classList.add('dark');
  else document.documentElement.classList.remove('dark');

window.addEventListener("toogle-darkmode", e => {
  if (darkExpected()) localStorage.theme = 'light';
  else localStorage.theme = 'dark';


// trigger
// phx-click={JS.dispatch("toogle-darkmode")}

You can use ETS to set up the initial value in the application

#start the ets lookup table, [:named_table, {:read_concurrency, true}])

#set theme_mode default
:ets.insert(:my_ets, {"theme_mode", :light})  
## true

add your toggle button

##toggle button
<button phx-click="toggle_mode">Toggle theme</button>

Monitor the click event

def handle_event("toggle_mode", _params, socket) do  
   |> then( fn mode ->
   {:reply, %{mode: mode}, socket} end)

#toggle theme mode
defp toggle_theme_mode(socket) do
   case socket.assigns.theme_mode do
       :dark  -> assign(socket, :theme_mode, :light)
       :light  -> assign(socket, :theme_mode, :dark)

include class={@theme_mode} in your template.

Just pointing up something that can help others: you should use color-scheme: dark; because it sets some CSS standards to dark color like inputs, scrollbars, and so on.

edit: some reference

use the power of AlpineJS?

Here is the tutorial on AlpineJS:
here is how to add AlpineJS to Phoenix: How to combine Phoenix LiveView with Alpine.js - Tutorials and screencasts for Elixir, Phoenix and LiveView (Steps 1 & 2)

I know writing JS is not something Elixir devs can be fan of, but I think implementation of dark/light mode is elegant with the help of AlpineJS. Also in this case, JS is not interacting with server and dealing solely with client-side feature, that makes it even more appropriate to use it in this case instead of making round-trips to the server LiveView way

1 Like

so I went thru tailwind’s documentation, seems like you will need to add “dark” class to html tag and all dark:styles will be used

1 Like

Based on the answers above, this is what I’m using:

// assets/tailwind.config.js
module.exports = {
  darkMode: 'class',
// assets/js/app.js:
function applyColorSchemePreference() {
    const darkExpected = window.matchMedia('(prefers-color-scheme: dark)').matches;
    if (darkExpected) {
        document.documentElement.classList.add('dark');'color-scheme', 'dark');
    else {
        document.documentElement.classList.remove('dark');'color-scheme', 'light');

// set listener to update color scheme preference on change
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
    const newColorScheme = event.matches ? "dark" : "light";

// check color scheme preference on page load

This doesn’t allow for manual toggling: it defers to the operating system. But it toggles automatically whenever the operating system switches between dark & light modes.

1 Like

Thanks dude, this worked beautifully!