Theme management with Solid

I would like to have a Dark and Light Mode on my page. For this I use Solid in combination with Astro.
<html> <!-- index.astro -->
<body>
<Scaffold client:only>
<Header client:load />
<main>
<slot />
</main>
<Footer />
</Scaffold>
</body>
</html>
<html> <!-- index.astro -->
<body>
<Scaffold client:only>
<Header client:load />
<main>
<slot />
</main>
<Footer />
</Scaffold>
</body>
</html>
How would you proceed now? My current status is that I have created a wrapper object "Scaffold" on the Astro page. Scaffold is a solidjs component which looks like this:
// Scaffold.tsx
import { Component, ErrorBoundary, JSX, Suspense } from "solid-js";
import { createThemeSignal } from "../../theme";

export interface ScaffoldProps {
children?: JSX.Element | JSX.Element[];
}

export const Scaffold: Component<ScaffoldProps> = (props: ScaffoldProps) => {
const [theme] = createThemeSignal();

return <div class="scaffold" data-theme={theme()}>
<Suspense fallback={<div>Loading...</div>}>
<ErrorBoundary fallback={err => <div>Errored: {err}</div>}>
{props.children}
</ErrorBoundary>
</Suspense>
</div>
};
// Scaffold.tsx
import { Component, ErrorBoundary, JSX, Suspense } from "solid-js";
import { createThemeSignal } from "../../theme";

export interface ScaffoldProps {
children?: JSX.Element | JSX.Element[];
}

export const Scaffold: Component<ScaffoldProps> = (props: ScaffoldProps) => {
const [theme] = createThemeSignal();

return <div class="scaffold" data-theme={theme()}>
<Suspense fallback={<div>Loading...</div>}>
<ErrorBoundary fallback={err => <div>Errored: {err}</div>}>
{props.children}
</ErrorBoundary>
</Suspense>
</div>
};
There I import a "signal" for the theme:
// theme.ts
import { createStore } from "solid-js/store";

export enum Theme {
Light = "light",
Dark = "dark"
}

interface StoreProps {
theme?: Theme | "system"
}

export function createThemeSignal() {
const [store, setStore] = createStore({} as StoreProps);
if (!document.documentElement.hasAttribute("data-theme")) {
document.documentElement.setAttribute("data-theme", store.theme ?? "system");
}

let setTheme = (theme: Theme | "system") => {
document.documentElement.setAttribute("data-theme", theme);
setStore({ theme });
}

let getTheme = () => {
return store.theme ?? "system";
}

return [getTheme, setTheme] as const;
}
// theme.ts
import { createStore } from "solid-js/store";

export enum Theme {
Light = "light",
Dark = "dark"
}

interface StoreProps {
theme?: Theme | "system"
}

export function createThemeSignal() {
const [store, setStore] = createStore({} as StoreProps);
if (!document.documentElement.hasAttribute("data-theme")) {
document.documentElement.setAttribute("data-theme", store.theme ?? "system");
}

let setTheme = (theme: Theme | "system") => {
document.documentElement.setAttribute("data-theme", theme);
setStore({ theme });
}

let getTheme = () => {
return store.theme ?? "system";
}

return [getTheme, setTheme] as const;
}
12 Replies
Robin Lindner
Robin LindnerOP2y ago
Now my question is how would you apply a style based on the theme. I like to work with Sass/Scss very much. By default, the system theme should always be used and the user should be able to change the theme manually using a button. Already created a _config.scss for the theme colors.
apollo79
apollo792y ago
I'd use a combination of CSS variables, a media query and JS to change on button click, like so:
@mixin light-theme {
--text-color: black;
--bg-color: white;
// more vars
}

@mixin dark-theme {
--text-color: white;
--bg-color: black;
// more vars
}

:root {
@include light-theme;

&[data-theme="dark"] {
@include dark-theme;
}

@media (prefers-color-scheme: dark) {
@inlcude dark-theme;

&[data-theme="light"] {
@inlcue light-theme;
}
}
}

// use the vars in your selectors
@mixin light-theme {
--text-color: black;
--bg-color: white;
// more vars
}

@mixin dark-theme {
--text-color: white;
--bg-color: black;
// more vars
}

:root {
@include light-theme;

&[data-theme="dark"] {
@include dark-theme;
}

@media (prefers-color-scheme: dark) {
@inlcude dark-theme;

&[data-theme="light"] {
@inlcue light-theme;
}
}
}

// use the vars in your selectors
And some JS, which sets the data-theme attribute on the root (document.querySelector("root")) when the button is clicked. Two tips: Use the dataset attribute instead of setAttribute("data-*") and you don't need a store here. Stores are for nested state that needs to get tracked granuarly And you could also store the theme in localstorage to persist it
Robin Lindner
Robin LindnerOP2y ago
And how would you use sass functions on css-variables? For ex. darken(var(--primary), 10%) does not work
apollo79
apollo792y ago
I would set the vars to the darkened value:
$light-primary: /* color */;
$light-primary-darkened: darken($light-primary, 10%);
// ...

@use "./_config.scss"

:root {
--primary: $light-primary;
--primary-darkened: $light-primary-darkened;
// ...
}

// ...
$light-primary: /* color */;
$light-primary-darkened: darken($light-primary, 10%);
// ...

@use "./_config.scss"

:root {
--primary: $light-primary;
--primary-darkened: $light-primary-darkened;
// ...
}

// ...
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
Robin Lindner
Robin LindnerOP2y ago
What bothers me is that it flickers a bit at first load.... However, I do not want that
apollo79
apollo792y ago
Is your default theme dark or light?
Robin Lindner
Robin LindnerOP2y ago
my default theme is dark. but someone's default theme might be light
apollo79
apollo792y ago
Does it flicker without the localstorage stuff too?
Robin Lindner
Robin LindnerOP2y ago
I have not tried it on my end. @RobBobs website just flickered.
apollo79
apollo792y ago
You should try on your end, as far as Isee @RobBobs site has no media query in CSS. And if the default theme of your site is light, then you should of course call setTheme("light") in the else clause. Or, better, just do nothing there and let the CSS do the stuff as long as the user doesn't select a theme actively
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
Want results from more Discord servers?
Add your server