# Theming
Corex UI design system is built to be fully themeable.
We offer over different themes, each available in light and dark modes.
neo, uno, duo, leo
# Apply Theme
To apply a theme globally, add the following attributes to your <html> tag replacing neo and light by the theme name and mode:
<html data-theme="neo" data-mode="light"></html>
Then, import the desired theme CSS:
@import "@corex-ui/design/themes/neo/light.css";
@import "@corex-ui/design/themes/neo/dark.css";
@import "@corex-ui/design/themes/uno/light.css";
@import "@corex-ui/design/themes/uno/dark.css";
@import "@corex-ui/design/themes/duo/light.css";
@import "@corex-ui/design/themes/duo/dark.css";
@import "@corex-ui/design/themes/leo/light.css";
@import "@corex-ui/design/themes/leo/dark.css";
# Theme Switching
Corex UI supports dynamic theme and mode switching using:
- Select component for themes
- Toggle Group component for modes
We also synchronize all components through the Machine API when a value changes.
Finally, an inline script is required to prevent Flash of Unstyled Content (FOUC).
- Add HTML
<div
id="selecter-theming"
class="select-js select select--sm hidden lg:flex"
data-on-value-change="update-selecter"
data-close-on-select="false"
data-no-trigger-update="true"
>
<div data-part="root" class="max-w-(--container-micro-sm)">
<div data-part="control" class="max-w-(--container-micro-sm)">
<div data-part="label" class="sr-only">Select theme</div>
<button data-part="trigger" class="gap-ui-gap-sm">
Theme
<svg
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="m19.5 8.25-7.5 7.5-7.5-7.5"
></path>
</svg>
</button>
</div>
<div data-part="positioner">
<ul data-part="content" class="scrollbar">
<li data-part="item" data-value="neo" class="flex items-center gap-2">
<span data-part="item-text" data-value="neo">Neo</span>
</li>
<li data-part="item" data-value="uno" class="group">
<span data-part="item-text" data-value="uno">Uno</span>
</li>
<li data-part="item" data-value="duo">
<span data-part="item-text" data-value="duo">Duo</span>
</li>
<li data-part="item" data-value="leo">
<span data-part="item-text" data-value="leo">Leo</span>
</li>
</ul>
</div>
</div>
</div>
<script>
try {
const theme = localStorage.getItem("data-theme");
console.log;
const switcher = document.querySelector("#selecter-theming");
if (switcher && theme) {
switcher.setAttribute("data-default-value", theme);
}
} catch (_) {}
</script>
<div
id="switcher-theming"
class="toggle-group toggle-group-js toggle-group--sm hidden lg:flex"
data-on-value-change="update-switcher"
>
<div data-part="root" class="rounded-full">
<button
data-part="item"
data-value="dark"
aria-label="Toggle light mode"
class="group data-[state=on]:bg-ui data-[state=on]:text-ui--text aspect-square rounded-full p-0"
>
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="256"
height="256"
viewBox="0 0 256 256"
xml:space="preserve"
>
<g
style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;"
transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)"
>
<rect
x="42"
y="0"
rx="0"
ry="0"
width="6"
height="15.79"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: currentColor; fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) "
/>
<rect
x="42"
y="74.21"
rx="0"
ry="0"
width="6"
height="15.79"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: currentColor; fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) "
/>
<rect
x="0"
y="42"
rx="0"
ry="0"
width="15.79"
height="6"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: currentColor; fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) "
/>
<rect
x="74.21"
y="42"
rx="0"
ry="0"
width="15.79"
height="6"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: currentColor; fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) "
/>
<path
d="M 74.698 11.059 l -15.71 15.71 C 54.99 23.689 50.129 22 45 22 c -12.682 0 -23 10.318 -23 23 c 0 5.13 1.689 9.991 4.769 13.989 L 11.059 74.698 l 4.242 4.242 L 78.94 15.301 L 74.698 11.059 z"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: currentColor; fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) "
stroke-linecap="round"
/>
<rect
x="15.76"
y="10.87"
rx="0"
ry="0"
width="6"
height="15.79"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: currentColor; fill-rule: nonzero; opacity: 1;"
transform=" matrix(0.7071 -0.7071 0.7071 0.7071 -7.7721 18.7634) "
/>
<rect
x="68.24"
y="63.34"
rx="0"
ry="0"
width="6"
height="15.79"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: currentColor; fill-rule: nonzero; opacity: 1;"
transform=" matrix(0.7071 -0.7071 0.7071 0.7071 -29.5071 71.2363) "
/>
</g>
</svg>
</button>
</div>
</div>
<script>
try {
const mode = localStorage.getItem("data-mode");
const switcher = document.querySelector("#switcher-theming");
if (switcher) {
switcher.setAttribute("data-default-value", mode);
}
} catch (_) {}
</script>
Inline script immediately after HTML ensures current values are populated from
localStorage.
- Add TypeScript
const ids = ["switcher-theming", "switcher-other"];
const elements = ids
.map((id) => document.getElementById(id))
.filter(Boolean) as HTMLElement[];
elements.forEach((el) => {
el.addEventListener("update-switcher", (event) => {
const { value } = (event as CustomEvent<{ value: string[] }>).detail;
const mode = value?.[0] ?? "light";
document.documentElement.setAttribute("data-mode", mode);
localStorage.setItem("data-mode", mode);
const source = event.currentTarget as HTMLElement;
elements.forEach((other) => {
if (other !== source) {
other.dispatchEvent(
new CustomEvent("toggle-group:set-value", { detail: { value } }),
);
}
});
});
});
const selecterIds = ["selecter-theming", "selecter-other"];
const selecterElements = selecterIds
.map((id) => document.getElementById(id))
.filter(Boolean) as HTMLElement[];
selecterElements.forEach((el) => {
el.addEventListener("update-selecter", (event) => {
const { value } = (event as CustomEvent<{ value: string[] }>).detail;
const theme = value?.[0] ?? "neo";
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("data-theme", theme);
const source = event.currentTarget as HTMLElement;
selecterElements.forEach((other) => {
if (other !== source) {
other.dispatchEvent(
new CustomEvent("select:set-value", { detail: { value } }),
);
}
});
});
});
- Add Inline Script to Prevent FOUC
Add this script at the very top of your <head>:
<script>
try {
const themeStored = localStorage.getItem("data-theme");
const theme = themeStored || "neo";
document.documentElement.setAttribute("data-theme", theme);
if (!themeStored) {
localStorage.setItem("data-theme", theme);
}
const modeStored = localStorage.getItem("data-mode");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
const mode = modeStored || (prefersDark ? "dark" : "light");
document.documentElement.setAttribute("data-mode", mode);
if (!modeStored) {
localStorage.setItem("data-mode", mode);
}
} catch (_) {}
</script>
- Import Components
Import Toggle Group and Select components in your main.ts
import "@corex-ui/static/components/select";
import "@corex-ui/static/components/toggle-group";
- Import CSS
Import Toggle Group and Select style and all themes needed in your style.css
@import "@corex-ui/design/themes/neo/light.css";
@import "@corex-ui/design/themes/neo/dark.css";
@import "@corex-ui/design/themes/uno/light.css";
@import "@corex-ui/design/themes/uno/dark.css";
@import "@corex-ui/design/themes/duo/light.css";
@import "@corex-ui/design/themes/duo/dark.css";
@import "@corex-ui/design/themes/leo/light.css";
@import "@corex-ui/design/themes/leo/dark.css";
@import "@corex-ui/design/components/select.css";
@import "@corex-ui/design/components/toggle-group.css";
You can also add a background to your body <body class="typo bg-root">
# Custom Dark Variant
If you wish to use the Tailwind custom variant such as dark: you must add the following to your main css
@custom-variant dark (&:where([data-mode=dark], [data-mode=dark] *));
# Automatic Mode Switching
If you want to automatically switch modes based on the user’s system preference you can use the following script
<script>
try {
const stored = localStorage.getItem("data-mode");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
const mode = stored || (prefersDark ? "dark" : "light");
document.documentElement.setAttribute("data-mode", mode);
if (!stored) {
localStorage.setItem("data-mode", mode);
}
} catch (_) {}
</script>
This setup ensures that:
- Themes and modes are synchronized across all components.
- User preferences are stored in
localStorage. - The page prevents FOUC on load.
- Automatic mode switching respects system preferences.