Blog

Customising the Oat UI theme

Full Oat default theme for reference, this site overrides, and how to customise it.


Customising the Oat UI theme

This site styles with Oat UI. Oat ships its defaults in 01-theme.css, loaded from @knadh/oat/oat.min.css. Our overrides live in src/lib/global.css, loaded after Oat via src/layouts/BaseHead.astro, so our values win. Only values that differ from Oat defaults are kept in global.css.

How to customise

  1. Override a variable in src/lib/global.css under :root. Example: change the brand colour.
  2. Use light-dark(light, dark) for values that follow the OS colour scheme.
  3. Force one mode with the theme switcher: it sets data-theme="light|dark" on <html>. global.css pins color-scheme under that selector (which forces native light-dark() in dev) plus the --lightningcss-light/--lightningcss-dark switch variables (which is what the production build actually reads — it compiles light-dark() away into an OS-media-query-only polyfill, so pinning color-scheme alone does nothing in prod).
  4. Layout knobs: --container-max caps .container, --grid-gap spaces .row columns, --sidebar-width sizes the sidebar layout.
:root {
	--primary: light-dark(#3e63dd, #3e63dd);
	--container-max: 1080px;
	--grid-gap: 1rem;
}

This site’s overrides (src/lib/global.css)

:root {
	color-scheme: light dark;
	--primary: light-dark(#3e63dd, #3e63dd);
	--primary-foreground: light-dark(#ffffff, #ffffff);
	--secondary-foreground: light-dark(#09090b, #fafafa);
	--ring: light-dark(#4f46e5, #a5b4fc);
	--radius-small: 0;
	--radius-medium: 0;
	--radius-large: 0;
	--radius-full: 0;
	--font-sans: var(--font-inter), system-ui, sans-serif;
	--container-max: 1080px;
	--grid-gap: 1rem;
}

Everything else (background, spacing scale, typography, shadows, motion, stacking) is identical to Oat defaults, so it is not repeated here.

Full Oat default theme reference (@knadh/oat@0.8.0, css/01-theme.css)

:root {
	color-scheme: light dark;

	--background: light-dark(#fff, #09090b);
	--foreground: light-dark(#09090b, #fafafa);
	--card: light-dark(#fff, #18181b);
	--card-foreground: light-dark(#09090b, #fafafa);
	--primary: light-dark(#574747, #fafafa);
	--primary-foreground: light-dark(#fafafa, #18181b);
	--secondary: light-dark(#f4f4f5, #27272a);
	--secondary-foreground: light-dark(#574747, #fafafa);
	--muted: light-dark(#f4f4f5, #27272a);
	--muted-foreground: light-dark(#71717a, #a1a1aa);
	--faint: light-dark(#fafafa, #1e1e21);
	--faint-foreground: light-dark(#a1a1aa, #71717a);
	--accent: light-dark(#f4f4f5, #27272a);
	--danger: light-dark(#d32f2f, #f4807b);
	--danger-foreground: light-dark(#fafafa, #18181b);
	--success: light-dark(#008032, #6cc070);
	--success-foreground: light-dark(#fafafa, #18181b);
	--warning: light-dark(#a65b00, #f0a030);
	--warning-foreground: #09090b;
	--border: light-dark(#d4d4d8, #52525b);
	--input: light-dark(#d4d4d8, #52525b);
	--ring: light-dark(#574747, #d4d4d8);

	--space-1: 0.25rem;
	--space-2: 0.5rem;
	--space-3: 0.75rem;
	--space-4: 1rem;
	--space-5: 1.25rem;
	--space-6: 1.5rem;
	--space-8: 2rem;
	--space-10: 2.5rem;
	--space-12: 3rem;
	--space-14: 3.5rem;
	--space-16: 4rem;
	--space-18: 4.5rem;

	--radius-small: 0.125rem;
	--radius-medium: 0.375rem;
	--radius-large: 0.75rem;
	--radius-full: 9999px;

	--bar-height: 0.5rem;

	--font-sans: system-ui, sans-serif;
	--font-mono: ui-monospace, Consolas, monospace;

	--text-1: clamp(1.75rem, 1.5rem + 1.1vw, 2.25rem);
	--text-2: clamp(1.5rem, 1.3rem + 0.8vw, 1.875rem);
	--text-3: clamp(1.25rem, 1.1rem + 0.5vw, 1.5rem);
	--text-4: clamp(1.125rem, 1.05rem + 0.3vw, 1.25rem);
	--text-5: 1.125rem;
	--text-6: 1rem;
	--text-7: 0.875rem;
	--text-8: 0.75rem;
	--text-regular: var(--text-6);

	--leading-normal: 1.5;

	--font-normal: 400;
	--font-medium: 500;
	--font-semibold: 600;
	--font-bold: 600;

	--shadow-small: 0 1px 2px 0 rgb(0 0 0 / 0.05);
	--shadow-medium:
		0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
	--shadow-large:
		0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);

	--transition-fast: 120ms cubic-bezier(0.4, 0, 0.2, 1);
	--transition: 200ms cubic-bezier(0.4, 0, 0.2, 1);

	--z-dropdown: 50;
	--z-modal: 200;
}

[data-variant='success'] {
	--_variant-color: var(--success);
}

[data-variant='warning'] {
	--_variant-color: var(--warning);
}

:is([data-variant='danger'], [data-variant='error']) {
	--_variant-color: var(--danger);
}

Layout defaults from Oat (css/grid.css, css/sidebar.css):

:root {
	--grid-cols: 12;
	--grid-gap: 1.5rem;
	--container-max: 1280px;
	--container-pad: 1rem;
	--sidebar-width: 15rem;
}