Styling
What this package puts on screen is one class, dark on <html>, and nothing else. What changes under it is decided by CSS. This page covers styling with @k8ordo/ui, with Tailwind CSS alone and with plain CSS, and how the class relates to the CSS color-scheme property and to the contrast settings of the OS.
The dark class on <html>
Neither the name nor where it goes can change: <html> carries dark when the result is dark, and nothing when it is light — there is no light class. The inline script puts it on before the first paint, and the provider’s effect toggles it afterwards. Any stylesheet that reads the class follows the visitor’s choice.
With @k8ordo/ui
@k8ordo/ui’s semantic tokens hold the light values on :root and the dark ones on .dark, in styles.css and tailwind.css alike, so the components and utilities such as bg-bg-base follow the class with nothing else to set up. @k8ordo/ui never adds the class itself; this package does.
/* src/styles/globals.css */
@import '@k8ordo/ui/tailwind.css';tailwind.css also declares the dark: and light: variants to read the class — dark: applies under .dark and light: everywhere else — so they work in your own markup as they are.
// src/components/logo.tsx
export function Logo() {
return (
<div className="bg-bg-base text-fg-base rounded-md p-4">
<img alt="k8ordo" className="dark:invert" src="/logo.svg" />
</div>
);
}The CSS color-scheme property
The color-scheme property decides whether the browser draws what it draws itself — scrollbars, form controls, a <dialog>’s default colours — light or dark. This package does not set it; @k8ordo/ui’s base layer declares it next to its tokens.
/* @k8ordo/ui's base layer */
:root {
color-scheme: light;
}
.dark {
color-scheme: dark;
}Not color-scheme: light dark, because then the browser picks by the OS’s prefers-color-scheme and disagrees with what the visitor chose here: with the OS dark and the visitor on light, the scrollbars have to stay light too. The property follows the same class as the tokens.
High contrast follows the OS
Contrast is not an axis this package owns. prefers-contrast: more and forced-colors: active are settings a visitor makes in the OS, not something an application stores or toggles, so neither appears in useColorScheme() or in the stored row.
@k8ordo/ui’s stylesheet follows both on its own. Under prefers-contrast: more it moves the text and border tokens further from the ground, on :root and on .dark alike, so it applies whichever scheme the visitor chose. Under forced-colors: active the browser repaints every colour from the visitor’s palette, and the components keep their boundaries, focus rings and selected states in system colours.
The two axes combine: a visitor who chose dark and asks the OS for more contrast gets the dark high-contrast values.
@k8ordo/ui theming: high contrast, forced colours, and your own UI under them
With Tailwind CSS alone
Tailwind CSS 4’s dark: variant reads prefers-color-scheme by default, so on its own it follows the OS and ignores the visitor’s choice. Redeclare it to read the class — the same declaration @k8ordo/ui’s tailwind.css makes.
/* src/styles/globals.css */
@import 'tailwindcss';
@custom-variant dark (&:where(.dark, .dark *));With plain CSS
Tie the colours to the class. Without @k8ordo/ui nothing declares the color-scheme property, so declare it next to the colours if the browser’s own rendering, such as form controls and scrollbars, should follow too.
/* src/styles/globals.css */
:root {
color-scheme: light;
--page-bg: #ffffff;
--page-fg: #1f1f1f;
}
:root.dark {
color-scheme: dark;
--page-bg: #1f1f1f;
--page-fg: #f5f5f5;
}
body {
background: var(--page-bg);
color: var(--page-fg);
}