# 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">
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 aspect-square rounded-full data-[state=on]:bg-ui data-[state=on]:text-ui--text 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>
# 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.