You are looking at the documentation of the work in progress Hope UI 1.0, examples and information may be broken or outdated.

Theming

Color mode

Adjust your application's color scheme based on user preference.

Usage

Hope UI comes with built-in support for managing color mode in your application. All components are color-mode aware.

By default, the color mode is stored in localStorage and appends a class to the body element.

Color mode script

The ColorModeScript ensures that the color mode storage sync works properly, and needs to be added before the content inside the body tag.

For a standard SolidJS application, you need to add the ColorModeScript to the index.tsx (or .jsx) file.

tsx
// index.tsx
import { ColorModeScript, HopeProvider } from "@hope-ui/core";
import { render } from "solid-js/web";
import App from "./App";
render(
() => (
<>
<ColorModeScript />
<HopeProvider>
<App />
</HopeProvider>
</>
),
document.getElementById("root") as HTMLElement
);
tsx
// index.tsx
import { ColorModeScript, HopeProvider } from "@hope-ui/core";
import { render } from "solid-js/web";
import App from "./App";
render(
() => (
<>
<ColorModeScript />
<HopeProvider>
<App />
</HopeProvider>
</>
),
document.getElementById("root") as HTMLElement
);

For a SolidStart application, you need to add the ColorModeScript to the root.tsx file.

tsx
// root.tsx
import {
ColorModeScript,
cookieStorageManagerSSR,
HopeProvider,
injectCriticalStyle,
} from "@hope-ui/core";
import { Suspense, useContext } from "solid-js";
import {
Body,
ErrorBoundary,
FileRoutes,
Head,
Html,
Meta,
Routes,
Scripts,
ServerContext,
Title,
} from "solid-start";
export default function Root() {
injectCriticalStyle();
return (
<Html lang="en">
<Head>
<Title>SolidStart - Bare</Title>
<Meta charset="utf-8" />
<Meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<Body>
<ColorModeScript />
<HopeProvider>
<Suspense>
<ErrorBoundary>
<Routes>
<FileRoutes />
</Routes>
</ErrorBoundary>
</Suspense>
</HopeProvider>
<Scripts />
</Body>
</Html>
);
}
tsx
// root.tsx
import {
ColorModeScript,
cookieStorageManagerSSR,
HopeProvider,
injectCriticalStyle,
} from "@hope-ui/core";
import { Suspense, useContext } from "solid-js";
import {
Body,
ErrorBoundary,
FileRoutes,
Head,
Html,
Meta,
Routes,
Scripts,
ServerContext,
Title,
} from "solid-start";
export default function Root() {
injectCriticalStyle();
return (
<Html lang="en">
<Head>
<Title>SolidStart - Bare</Title>
<Meta charset="utf-8" />
<Meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<Body>
<ColorModeScript />
<HopeProvider>
<Suspense>
<ErrorBoundary>
<Routes>
<FileRoutes />
</Routes>
</ErrorBoundary>
</Suspense>
</HopeProvider>
<Scripts />
</Body>
</Html>
);
}

You can also use a cookie storage manager instead, like below.

tsx
// root.tsx
import {
ColorModeScript,
cookieStorageManagerSSR,
HopeProvider,
injectCriticalStyle,
} from "@hope-ui/core";
import { Suspense, useContext } from "solid-js";
import {
Body,
ErrorBoundary,
FileRoutes,
Head,
Html,
Meta,
Routes,
Scripts,
ServerContext,
Title,
} from "solid-start";
export default function Root() {
const event = useContext(ServerContext);
// or import the default `cookieStorageManager` from "@hope-ui/core"
const storageManager = cookieStorageManagerSSR(
isServer ? event.request.headers.get("cookie") ?? "" : document.cookie
);
injectCriticalStyle();
return (
<Html lang="en">
<Head>
<Title>SolidStart - Bare</Title>
<Meta charset="utf-8" />
<Meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<Body>
<ColorModeScript storageType="cookie" />
<HopeProvider storageManager={storageManager}>
<Suspense>
<ErrorBoundary>
<Routes>
<FileRoutes />
</Routes>
</ErrorBoundary>
</Suspense>
</HopeProvider>
<Scripts />
</Body>
</Html>
);
}
tsx
// root.tsx
import {
ColorModeScript,
cookieStorageManagerSSR,
HopeProvider,
injectCriticalStyle,
} from "@hope-ui/core";
import { Suspense, useContext } from "solid-js";
import {
Body,
ErrorBoundary,
FileRoutes,
Head,
Html,
Meta,
Routes,
Scripts,
ServerContext,
Title,
} from "solid-start";
export default function Root() {
const event = useContext(ServerContext);
// or import the default `cookieStorageManager` from "@hope-ui/core"
const storageManager = cookieStorageManagerSSR(
isServer ? event.request.headers.get("cookie") ?? "" : document.cookie
);
injectCriticalStyle();
return (
<Html lang="en">
<Head>
<Title>SolidStart - Bare</Title>
<Meta charset="utf-8" />
<Meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<Body>
<ColorModeScript storageType="cookie" />
<HopeProvider storageManager={storageManager}>
<Suspense>
<ErrorBoundary>
<Routes>
<FileRoutes />
</Routes>
</ErrorBoundary>
</Suspense>
</HopeProvider>
<Scripts />
</Body>
</Html>
);
}

To use the ColorModeScript on a site with strict Content-Security-Policy, you can use the nonce prop that will be passed to the script tag.

Initial color mode

The default color mode used by Hope UI is system, meaning it will adapt to user preference.

To set the initial color mode your application should start with, pass the initialColorMode prop to ColorModeScript and HopeProvider with either light, dark or system.

tsx
// index.tsx
import { ColorModeScript, HopeProvider } from "@hope-ui/core";
import { render } from "solid-js/web";
import App from "./App";
render(
() => (
<>
<ColorModeScript initialColorMode="dark" />
<HopeProvider initialColorMode="dark">
<App />
</HopeProvider>
</>
),
document.getElementById("root") as HTMLElement
);
tsx
// index.tsx
import { ColorModeScript, HopeProvider } from "@hope-ui/core";
import { render } from "solid-js/web";
import App from "./App";
render(
() => (
<>
<ColorModeScript initialColorMode="dark" />
<HopeProvider initialColorMode="dark">
<App />
</HopeProvider>
</>
),
document.getElementById("root") as HTMLElement
);

Changing color mode

To manage color mode in your application, Hope UI exposes the useColorMode primitive. It gives you an accessor to get the current color mode and a function to toggle it.

tsx
import { Button, useColorMode } from "@hope-ui/core";
function UseColorModeExample() {
const { colorMode, toggleColorMode } = useColorMode();
return (
<Button onClick={toggleColorMode}>
Toggle {colorMode() === "light" ? "dark" : "light"} mode
</Button>
);
}
tsx
import { Button, useColorMode } from "@hope-ui/core";
function UseColorModeExample() {
const { colorMode, toggleColorMode } = useColorMode();
return (
<Button onClick={toggleColorMode}>
Toggle {colorMode() === "light" ? "dark" : "light"} mode
</Button>
);
}

Changing a value based on color mode

The useColorModeValue primitive can be used to change any value or style based on the color mode.

It takes 2 arguments: the value in light and dark mode, and returns a derived signal with the correct value based on the color mode.

The below example demonstrates how to change a Box's background and color using useColorModeValue.

This box's style will change based on the color mode.
tsx
import { Box, Button, useColorMode, useColorModeValue } from "@hope-ui/core";
export function UseColorModeValueExample() {
const { toggleColorMode } = useColorMode();
const bg = useColorModeValue("tomato", "royalblue");
const color = useColorModeValue("black", "white");
return (
<>
<Box mb={4} p={2} bg={bg()} color={color()}>
This box's style will change based on the color mode.
</Box>
<Button onClick={toggleColorMode}>Toggle color mode</Button>
</>
);
}
tsx
import { Box, Button, useColorMode, useColorModeValue } from "@hope-ui/core";
export function UseColorModeValueExample() {
const { toggleColorMode } = useColorMode();
const bg = useColorModeValue("tomato", "royalblue");
const color = useColorModeValue("black", "white");
return (
<>
<Box mb={4} p={2} bg={bg()} color={color()}>
This box's style will change based on the color mode.
</Box>
<Button onClick={toggleColorMode}>Toggle color mode</Button>
</>
);
}

Styling

Using the light/dark object syntax

Style props accept an object as value in which you can set the light and/or dark value. The above example could also be achieved in this way:

tsx
import { Box, Button, useColorMode } from "@hope-ui/core";
export function UseDarkStylePropExample() {
const { toggleColorMode } = useColorMode();
return (
<>
<Box
mb={4}
p={2}
bg={{ light: "tomato", dark: "royalblue" }}
color={{ light: "black", dark: "white" }}
>
This box's style will change based on the color mode.
</Box>
<Button onClick={toggleColorMode}>Toggle color mode</Button>
</>
);
}
tsx
import { Box, Button, useColorMode } from "@hope-ui/core";
export function UseDarkStylePropExample() {
const { toggleColorMode } = useColorMode();
return (
<>
<Box
mb={4}
p={2}
bg={{ light: "tomato", dark: "royalblue" }}
color={{ light: "black", dark: "white" }}
>
This box's style will change based on the color mode.
</Box>
<Button onClick={toggleColorMode}>Toggle color mode</Button>
</>
);
}

The _dark style prop

If you prefer to set all dark mode styles in the same place, Hope UI provides a _dark style prop. Below is the same example achieved this way:

tsx
import { Box, Button, useColorMode } from "@hope-ui/core";
export function UseDarkStylePropExample() {
const { toggleColorMode } = useColorMode();
return (
<>
<Box
mb={4}
p={2}
bg="tomato"
color="black"
_dark={{
bg: "royalblue",
color: "white",
}}
>
This box's style will change based on the color mode.
</Box>
<Button onClick={toggleColorMode}>Toggle color mode</Button>
</>
);
}
tsx
import { Box, Button, useColorMode } from "@hope-ui/core";
export function UseDarkStylePropExample() {
const { toggleColorMode } = useColorMode();
return (
<>
<Box
mb={4}
p={2}
bg="tomato"
color="black"
_dark={{
bg: "royalblue",
color: "white",
}}
>
This box's style will change based on the color mode.
</Box>
<Button onClick={toggleColorMode}>Toggle color mode</Button>
</>
);
}

Caveat

Both the light/dark object syntax and _dark style prop doesn't work inside css pseudo-element selector like ::before.

For example if you want to style the placeholder of an input in dark mode the following code will not work:

tsx
<input
sx={{
"&::placeholder": {
color: {
light: "neutral.500",
dark: "neutral.600", // ❌ Doesn't work
},
},
}}
/>
// Or
<input
sx={{
"&::placeholder": {
color: "neutral.500",
_dark: {
color: "neutral.600", // ❌ Doesn't work too
},
},
}}
/>
tsx
<input
sx={{
"&::placeholder": {
color: {
light: "neutral.500",
dark: "neutral.600", // ❌ Doesn't work
},
},
}}
/>
// Or
<input
sx={{
"&::placeholder": {
color: "neutral.500",
_dark: {
color: "neutral.600", // ❌ Doesn't work too
},
},
}}
/>

Instead, you have to put the css pseudo-element selector inside the _dark pseudo selector.

tsx
<input
sx={{
"&::placeholder": {
color: "neutral.500",
},
_dark: {
"&::placeholder": {
color: "neutral.600", // ✅ Work
},
},
}}
/>
tsx
<input
sx={{
"&::placeholder": {
color: "neutral.500",
},
_dark: {
"&::placeholder": {
color: "neutral.600", // ✅ Work
},
},
}}
/>
Previous
CSS variables