css
@layer reset, arkyn;@layer reset {* {margin: 0;padding: 0;box-sizing: border-box;}*,input,textarea,button {font-family: "Open Sans", sans-serif;font-optical-sizing: auto;}}
line-height: 130% or any other line-height
value in unlayered global CSS. Keep this rule inside the reset layer so
it does not interfere with Arkyn component styles. In some cases,
developers may think text is not vertically centered, when the real cause
is the global line-height affecting the component's internal layout.css
:root {--white: #ffffff;/* background */--background: #f4f4f5;--background-underground: #fafafa;--background-foreground: #ffffff;/* text */--text-heading: #09090b;--text-body: #52525b;--text-muted: #a1a1aa;/* border */--border: #e2e8f0;/* inputs, textarea, select, checkbox, etc. */--input-background: #fafafa;/* tooltip */--tooltip-text: #fafafa;--tooltip-background: #27272a;/* spotlight */--spotlight-primary: 17, 109, 220; /* #116ddc */--spotlight-secondary: 100, 116, 139; /* #64748B */--spotlight-success: 16, 185, 129; /* #10b981 */--spotlight-danger: 244, 63, 94; /* #f43f5e */--spotlight-info: 14, 165, 233; /* #0ea5e9 */--spotlight-warning: 249, 115, 22; /* #f97316 */--spotlight-primary-foreground: 231, 240, 252; /* #E7F0FC */--spotlight-secondary-foreground: 250, 250, 250; /* #fafafa */--spotlight-success-foreground: 231, 248, 242; /* #E7F8F2 */--spotlight-danger-foreground: 254, 236, 239; /* #FEECEF */--spotlight-info-foreground: 231, 246, 253; /* #E7F6FD */--spotlight-warning-foreground: 254, 241, 232; /* #FEF1E8 */}
:root, scope each set behind a selector that checks whether the <html> element carries a dark class. The light set stays the default, and the dark set overrides it whenever the class is present.css
html:not(.dark):root {--white: #ffffff;/* background */--background: #f4f4f5;--background-underground: #fafafa;--background-foreground: #ffffff;/* text */--text-heading: #09090b;--text-body: #52525b;--text-muted: #a1a1aa;/* border */--border: #e2e8f0;/* inputs, textarea, select, checkbox, etc. */--input-background: #fafafa;/* tooltip */--tooltip-text: #fafafa;--tooltip-background: #27272a;/* spotlight */--spotlight-primary: 17, 109, 220; /* #116ddc */--spotlight-secondary: 100, 116, 139; /* #64748B */--spotlight-success: 16, 185, 129; /* #10b981 */--spotlight-danger: 244, 63, 94; /* #f43f5e */--spotlight-info: 14, 165, 233; /* #0ea5e9 */--spotlight-warning: 249, 115, 22; /* #f97316 */--spotlight-primary-foreground: 231, 240, 252; /* #E7F0FC */--spotlight-secondary-foreground: 250, 250, 250; /* #fafafa */--spotlight-success-foreground: 231, 248, 242; /* #E7F8F2 */--spotlight-danger-foreground: 254, 236, 239; /* #FEECEF */--spotlight-info-foreground: 231, 246, 253; /* #E7F6FD */--spotlight-warning-foreground: 254, 241, 232; /* #FEF1E8 */}html.dark:root {--white: #ffffff;/* background */--background: #09090b;--background-underground: #151518;--background-foreground: #18181b;/* text */--text-heading: #e4e4e7;--text-body: #a1a1aa;--text-muted: #71717a;/* border */--border: #2c2c30;/* inputs, textarea, select, checkbox, etc. */--input-background: #131316;/* tooltip */--tooltip-text: #fafafa;--tooltip-background: #3f3f46;/* spotlight */--spotlight-primary: 15, 98, 204; /* #0F62CC */--spotlight-secondary: 161, 161, 170; /* #A1A1AA */--spotlight-success: 14, 165, 115; /* #0EA573 */--spotlight-danger: 225, 55, 85; /* #E13755 */--spotlight-info: 12, 148, 210; /* #0C94D2 */--spotlight-warning: 234, 102, 18; /* #EA6612 */--spotlight-primary-foreground: 3, 28, 61; /* #475569 */--spotlight-secondary-foreground: 31, 31, 34; /* #1f1f22 */--spotlight-success-foreground: 2, 44, 34; /* #022C22 */--spotlight-danger-foreground: 76, 5, 25; /* #4C0519 */--spotlight-info-foreground: 8, 47, 73; /* #082F49 */--spotlight-warning-foreground: 69, 26, 3; /* #451A03 */}
:not(.dark) avoids a specificity tie:root, so without :not(.dark) the light block
and the dark block would have the same specificity and the last one
declared in the file would always win, regardless of the class. Scoping
the light block to :not(.dark) makes the two selectors mutually
exclusive.dark class on the <html> element. Since <html> is rendered outside the React root in a regular client-side app, the class has to be applied imperatively, not through JSX. A small hook that syncs a boolean state with document.documentElement is enough:tsx
import { useEffect, useState } from "react";function useDarkMode() {const [darkMode, setDarkMode] = useState(false);useEffect(() => {document.documentElement.classList.toggle("dark", darkMode);}, [darkMode]);return { darkMode, setDarkMode };}export { useDarkMode };
Switch:tsx
import { Switch } from "@arkyn/components/switch";import { useDarkMode } from "./useDarkMode";function ThemeToggle() {const { darkMode, setDarkMode } = useDarkMode();return (<SwitchunShowFieldTemplatename="darkMode"checked={darkMode}onCheck={() => setDarkMode((current) => !current)}/>);}export { ThemeToggle };
dark class, use a data-theme attribute whose value selects a named color palette. Each theme becomes its own block, and the default palette is the one that applies when the attribute is missing.css
html[data-theme="ocean"]:root {--background: #0b1727;--background-underground: #0f1f33;--background-foreground: #142c47;--text-heading: #e2e8f0;--text-body: #cbd5e1;--text-muted: #94a3b8;--border: #1e3a5f;--spotlight-primary: 56, 189, 248; /* #38BDF8 */--spotlight-success: 45, 212, 191; /* #2DD4BF */}html[data-theme="sunset"]:root {--background: #2b1710;--background-underground: #331c13;--background-foreground: #3d221a;--text-heading: #fef3e2;--text-body: #fde4c9;--text-muted: #c2a68d;--border: #55301f;--spotlight-primary: 251, 146, 60; /* #FB923C */--spotlight-danger: 248, 113, 113; /* #F87171 */}
data-theme set, if you defined one, and from
there to Arkyn's own built-in default for that token.tsx
import { useEffect, useState } from "react";function useAppTheme(defaultTheme = "light") {const [theme, setTheme] = useState(defaultTheme);useEffect(() => {if (theme === "light") {document.documentElement.removeAttribute("data-theme");} else {document.documentElement.setAttribute("data-theme", theme);}}, [theme]);return { theme, setTheme };}export { useAppTheme };
tsx
import { useAppTheme } from "./useAppTheme";function ThemePicker() {const { theme, setTheme } = useAppTheme();return (<select value={theme} onChange={(event) => setTheme(event.target.value)}><option value="light">Light</option><option value="ocean">Ocean</option><option value="sunset">Sunset</option></select>);}export { ThemePicker };
data-theme value instead of a separate class, if your app already needs more than two palettes.ts
import "<yourPath>/reset.css";import "@arkyn/components/styles";
reset, and reserve the arkyn layer for the library stylesheet.@arkyn/components/styles.localStorage) and applying it before the first paint avoids a visible flash of the wrong theme on load, but that step is optional and outside the scope of Arkyn itself.data-theme attribute, the rule stays the same: your app owns the selector on <html>, and Arkyn only reacts to the resulting CSS variables.