Theme System
The template supports full theme switching — brand color, fonts, dark/light mode, and layout variants — all config-driven with zero runtime JavaScript.
How it works
Section titled “How it works”Layout.astro reads src/config/theme.ts at build time and generates an inline <style> tag injected into <head>:
@font-face { font-family: "Inter Variable"; src: url("/fonts/inter-variable.woff2") format("woff2");}@font-face { font-family: "Plus Jakarta Sans Variable"; src: url("/fonts/plus-jakarta-sans-variable.woff2") format("woff2");}:root { --color-brand-50: #edfff6; --color-brand-100: #c8ffea; /* ... all 11 stops ... */ --font-sans: "Inter Variable", system-ui, sans-serif; --font-display: "Plus Jakarta Sans Variable", "Inter Variable", sans-serif;}This inline <style> appears after the <link rel="stylesheet"> in the HTML. In the CSS cascade, same-origin author stylesheets are ordered by source order — so the inline block always wins over the compiled Tailwind sheet.
src/styles/global.css declares @theme { --color-brand-*: <defaults>; } which Tailwind compiles into utility classes (bg-brand-500, text-brand-400, etc.) that reference those CSS custom properties. The inline :root override swaps the actual values — all utilities pick up the new palette automatically.
Result: build-time theming with zero runtime JS.
Switching theme presets
Section titled “Switching theme presets”Change the one import in src/config/theme.ts:
// Change this line to switch the entire site theme:import { darkGreen } from "../themes/dark-green"; // jujualpha default// import { darkBlue } from "../themes/dark-blue";// import { darkPurple } from "../themes/dark-purple";// import { lightNeutral } from "../themes/light-neutral";
export const theme: Theme = darkGreen;| Preset | Mode | Accent | Cards |
|---|---|---|---|
dark-green |
dark | Green (jujualpha) | bordered |
dark-blue |
dark | Blue | bordered |
dark-purple |
dark | Purple | elevated |
light-neutral |
light | Gray | flat |
Creating a custom palette
Section titled “Creating a custom palette”Copy src/themes/dark-green.ts and replace the brand color scale:
import type { Theme } from "./types";
export const myTheme: Theme = { mode: "dark", colors: { brand: { 50: "#fff7ed", 100: "#ffedd5", 200: "#fed7aa", 300: "#fdba74", 400: "#fb923c", 500: "#f97316", 600: "#ea6c0c", 700: "#c2540a", 800: "#9a3c06", 900: "#7c2d12", 950: "#431407", }, }, fonts: { sans: { family: "Inter Variable", src: "/fonts/inter-variable.woff2" }, display: { family: "Plus Jakarta Sans Variable", src: "/fonts/plus-jakarta-sans-variable.woff2" }, }, layout: { hero: "centered", nav: "fixed", cards: "bordered", },};Use uicolors.app to generate a full 50–950 scale from a single hex value.
For dark themes: 50 should be near-white, 950 near-black. 500 is typically the primary action color.
Then update the import in src/config/theme.ts:
import { myTheme } from "../themes/my-theme";export const theme: Theme = myTheme;Swapping fonts
Section titled “Swapping fonts”If the client has a different typeface:
- Get the
.woff2variable font file (Google Fonts → Download family → Variable) - Copy to
public/fonts/ - Update the theme file:
fonts: { sans: { family: "Nunito Variable", src: "/fonts/nunito-variable.woff2" }, display: { family: "Sora Variable", src: "/fonts/sora-variable.woff2" },},- Update the defaults in
src/styles/global.cssso Tailwind’sfont-sansandfont-displayutilities have the right fallback:
@theme { --font-sans: "Nunito Variable", system-ui, sans-serif; --font-display: "Sora Variable", "Nunito Variable", sans-serif;}Dark / light mode
Section titled “Dark / light mode”theme.mode sets "dark" or "light". Layout.astro adds the value as a class on <html>:
<html class:list={[theme.mode, "scroll-smooth"]}>Body background and text colour switch based on this class:
<body class:list={[ isDark ? "bg-gray-950 text-gray-100" : "bg-white text-gray-900", "font-sans antialiased"]}>Mode is set at build time per client. There is no dark/light toggle by default — add one later if the client requests it.
Layout variants
Section titled “Layout variants”theme.layout.hero
Section titled “theme.layout.hero”| Value | Layout |
|---|---|
"centered" |
Text and CTAs centered, single column |
"split" |
Text left, image/graphic right (two columns) |
"fullscreen" |
Full-bleed background, overlay text |
theme.layout.nav
Section titled “theme.layout.nav”| Value | Behaviour |
|---|---|
"fixed" |
Stays at top of viewport at all times |
"sticky" |
Scrolls away, then sticks when scrolling back up |
"static" |
Flows with page, no sticky behaviour |
theme.layout.cards
Section titled “theme.layout.cards”| Value | Style |
|---|---|
"bordered" |
border border-white/10, no shadow |
"elevated" |
shadow-lg shadow-black/20, no border |
"flat" |
Neither border nor shadow |
Components that render cards read theme.layout.cards and apply the correct class automatically.