PLATFORM
  • Tails

    Create websites with TailwindCSS

  • Blocks

    Design blocks for your website

  • Wave

    Start building the next great SAAS

  • Pines

    Alpine & Tailwind UI Library

  • Auth

    Plug'n Play Authentication for Laravel

  • Designer comingsoon

    Create website designs with AI

  • DevBlog comingsoon

    Blog platform for developers

  • Static

    Build a simple static website

  • SaaS Adventure

    21-day program to build a SAAS

Question By
Unsolved

Dark mode in user dashboard

produkt

Apr 3rd, 2025 09:57 AM

I have added several pages within my user dashboard including some links in the sidebar. When I enable dark mode from the user menu it enables dark mode, obviously. However, if I navigate to another page within the user dashboard the theme switches back to light mode even though the toggle shows it should still be in dark. I have to disable dark mode and then re-enable it to become dark again, OR refresh the page and it switches back to dark.

If I enable dark mode and reload the page, it stays dark. It seems just navigating to new pages doesn't keep the theme until I press refresh. Any ideas on why this is happening?

bobbyiliev

Apr 4th, 2025 11:12 PM

Hey there,

Does this happen only for the new pages that you've added or all of the existing pages as well?

It's likely that navigating between dashboard pages doesn’t trigger a full page reload, which can prevent the dark mode class from being reapplied properly.

A possible fix would be to add a small global script in your layout to set the dark class early, based on localStorage, before Alpine or Livewire load. This ensures the theme sticks between pages.

- Bobby

produkt

Apr 5th, 2025 04:54 AM

It happens on both. The only original dashboard page I have remaining is the change log, but it’s happening on my new custom pages as well as the change log. Do you have an example of such a script you recommend?

Also, I did a fresh install of wave in a test environment, and it occurs there too. On a demo install, there are no additional pages actually, they are all just test links with the exception of the changelog, which the problem occurs there.

lars3

Apr 9th, 2025 01:58 AM

Hello, I just started out a new project in Wave and I experience the same issue. When navigating through the menu the theme switches back to light. A hard refresh the theme is back to black. I can't make much sense of it but it happens on a fresh install, firefox and chrome.

lars3

Apr 9th, 2025 10:25 PM

Some additional information. Localstorage has theme: dark property.

On page reload the html tag is as follows

<html class="dark" lang="en">

On livewire refresh this toggles back to

<html class="dark nprogress-busy" lang="en">

After livewire refresh is done, this is the result, back to light mode.

<html lang="en">

Afterpage refresh back to

<html class="dark" lang="en">

I also noticed a livewire error in the javascript console of my browser, it might be related:

Uncaught SyntaxError: expected expression, got ')'
    safeAsyncFunction https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1174
    generateFunctionFromString https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1184
    generateEvaluatorFromString https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1189
    normalEvaluator https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1154
    evaluateLater https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1144
    handler2 https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:3532
    flushHandlers https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1281
    stopDeferring https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1286
    deferHandlingDirectives https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1289
    initTree https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1479
    start https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1428
    start https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:1427
    start2 https://wave.localhost/vendor/livewire/livewire.js?id=13b7c601:8813
bobbyiliev

Apr 10th, 2025 12:43 AM

Hi there,

I am trying to reproduce it locally with a fresh new install but it seems to be working as expected:

Do you by any chance have some steps that I could try out locally to reproduce it reliably?

lars3

Apr 10th, 2025 04:12 AM

Hello,

I just spend some time trying to get to the bottom of it. I tried to remove as much code as possible and the problem still persists.

Then I figured it might be platform dependend, as I'm running a Macbook with Herd. So I deployed the application on a server to see. But the problem persists. You can see it here if you register an account: https://aibeacon.eu/

As far as reproducing the problem, it also occurs on a fresh project download so I have very little to point at.

produkt

Apr 10th, 2025 05:20 AM

I'm not able to reproduce your results. Although, I see where some of the confusion is now. Sub-pages under "settings" preserve dark mode but the rest don't. Try navigating to pages that are not under "Settings." Try:

  • Changelog
  • Notifications
  • Public profile
  • Dashboard
lars3

Apr 10th, 2025 06:26 AM

The issue seems to occur on links with 'wire:navigate'. I am kind of working around the issue removing all 'wire:navigate' on the links. This way a hard reload is forced and dark mode is correctly applied. Although this does not solve the actual problem, it does not bother me anymore.

Report
1
produkt

Apr 10th, 2025 06:31 AM

That does not feel like a good option for me. wire:navigate speeds load time and is a better user experience. There must be a better way.

bobbyiliev

Apr 10th, 2025 06:33 AM

Seems like this is discussed here: https://github.com/livewire/livewire/discussions/9191

I will look into it and see if there is a way around the limitation.

produkt

May 31st, 2025 05:57 AM

Any updates on this?

produkt

Oct 7th, 2025 09:23 AM

Problem still persists even with recent livewire updates

produkt

Oct 8th, 2025 10:05 AM

This is the closet I've gotten, however there is still some slight flicker when navigating to certain pages. I'm not going to post a PR because I still don't think this solution is perfect unless we can solve the flicker, but it's better than what's happening now. Replace your app.blade.php with this.

Edit: the toggle stops functioning after you navigate to a new page in dark mode. I'm not sure this fix even helps the situation really. Please help!

<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
    @include('theme::partials.head', ['seo' => ($seo ?? null) ])

    <!-- Apply dark mode instantly before rendering (no initial flicker) -->
    <script>
        if (typeof(Storage) !== "undefined") {
            if (localStorage.getItem('theme') === 'dark') {
                document.documentElement.classList.add('dark');
            }
        }
    </script>
</head>

<body
    x-data="{
        darkMode: localStorage.getItem('theme') === 'dark' ||
                   (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)
    }"
    x-init="
        // Ensure correct class on load
        document.documentElement.classList.toggle('dark', darkMode);

        // Watch for changes and persist
        $watch('darkMode', value => {
            localStorage.setItem('theme', value ? 'dark' : 'light');
            document.documentElement.classList.toggle('dark', value);
        });
    "
    :class="{ 'dark': darkMode }"
    class="@if(config('wave.dev_bar')) pb-10 @endif"
>

<!-- Persistent shell: stays in DOM through Livewire navigation -->
<div id="persistent-shell"
     class="flex flex-col lg:min-h-screen bg-zinc-50 dark:bg-zinc-900 transition-none">

    <x-app.sidebar />

    <div class="flex flex-col min-h-screen pl-0 justify-stretch lg:pl-64">
        {{-- Mobile Header --}}
        <header class="lg:hidden px-5 block flex justify-between sticky top-0 z-40 bg-gray-50 dark:bg-zinc-900 -mb-px border-b border-zinc-200/70 dark:border-zinc-700 h-[72px] items-center">
            <button x-on:click="window.dispatchEvent(new CustomEvent('open-sidebar'))"
                    class="flex items-center justify-center flex-shrink-0 w-10 h-10 rounded-md text-zinc-700 dark:text-zinc-200 hover:bg-gray-200/70 dark:hover:bg-zinc-700/70">
                <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
                     viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round"
                          d="M3.75 9h16.5m-16.5 6.75h16.5" />
                </svg>
            </button>
            <x-app.user-menu position="top" />
        </header>
        {{-- End Mobile Header --}}

        <main class="flex flex-col flex-1 xl:px-0 lg:pt-4 lg:h-screen">
            <div class="flex-1 h-full overflow-hidden bg-white border-t border-l-0 lg:border-l
                            dark:bg-zinc-800 lg:rounded-tl-xl border-zinc-200/70 dark:border-zinc-700">
                <div class="w-full h-full px-5 sm:px-8 lg:overflow-y-scroll scrollbar-hidden lg:pt-5 lg:px-5">
                    {{ $slot }}
                </div>
            </div>
        </main>
    </div>

    @livewire('notifications')

    @if(!auth()->guest() && auth()->user()->hasChangelogNotifications())
        @include('theme::partials.changelogs')
    @endif

    @include('theme::partials.footer-scripts')
    {{ $javascript ?? '' }}
</div>
<!-- /persistent-shell -->

<!-- Ensure dark mode persists through Livewire navigation -->
<script>
    // Before Livewire swaps DOM, remember dark state
    document.addEventListener('livewire:navigating', () => {
        if (document.documentElement.classList.contains('dark')) {
            document.documentElement.dataset.keepDark = 'true';
        }
    });

    // After navigation completes, instantly restore dark class
    document.addEventListener('livewire:navigated', () => {
        if (localStorage.getItem('theme') === 'dark' ||
            document.documentElement.dataset.keepDark === 'true') {
            document.documentElement.classList.add('dark');
        } else {
            document.documentElement.classList.remove('dark');
        }
        delete document.documentElement.dataset.keepDark;
    });
</script>

<!-- Optional: prevent transitions during navigation to avoid subtle flashes but destroys all page animations
<style>
    html.dark, html.dark * {
        transition: none !important;
    }
</style> -->

</body>
</html>