Get Hedhog updates in your inbox
New releases, fresh recipes, and breaking changes — no spam.
Theming
Hedhog's admin panel is fully customizable through settings — colors, logo, favicon, app name, and typography — with no rebuild required. This is one of the most completely implemented recipes in Hedhog: every setting below is real, wired into apps/admin/src/components/provider/theme-provider.tsx, and takes effect immediately.
In the admin, all of this lives under Settings → Configurations → Appearance — there is no group literally named "Theme," even though every setting key below is prefixed theme-.
Status: ✅ Fully implemented — every setting below is real and takes effect on the next page load.

Quick Setup
- Go to Settings → Configurations → Appearance
- Set
theme-primary-lightandtheme-primary-dark(and the other color pairs) to your brand's hex colors - Upload your logo/favicon via File Storage, then paste the resulting URLs into
image-url/icon-url - Hard-refresh the admin and confirm the new colors and logo actually render
How It Works
- Theme settings are stored as rows in the
settingtable, under the Appearance configuration group - On load, the admin's
ThemeProviderreads them (cached inlocalStorageunder thesettingskey) and injects a<style>block straight into the document — each color setting becomes a CSS custom property like--primary, written as!importantso it overrides Tailwind's defaults - Colors are applied as raw hex strings, not converted to HSL — if a value isn't a valid
#rrggbbstring, that rule is silently skipped and the Tailwind default shows through instead - Hedhog tracks light and dark variants of every color independently — there is no single source color that gets auto-darkened; you set both explicitly
theme-modecontrols light/dark/system, and a per-user override is stored client-side inlocalStorageundertheme— so an individual user can flip to dark mode without changing the system default for everyone else
Color Settings
Every color below exists twice — once with a -light suffix and once with a -dark suffix — under Settings → Configurations → Appearance. Hedhog does not generate one from the other; if you only set the light variant, dark mode falls back to Tailwind's defaults for that token.
| CSS variable | Light setting key | Dark setting key | What it controls |
|---|---|---|---|
--primary | theme-primary-light | theme-primary-dark | Buttons, links, active states, focus rings |
--primary-foreground | theme-primary-foreground-light | theme-primary-foreground-dark | Text/icons on primary-colored backgrounds |
--secondary | theme-secondary-light | theme-secondary-dark | Secondary buttons, subtle accents |
--secondary-foreground | theme-secondary-foreground-light | theme-secondary-foreground-dark | Text on secondary backgrounds |
--accent | theme-accent-light | theme-accent-dark | Hover/interactive accents |
--accent-foreground | theme-accent-foreground-light | theme-accent-foreground-dark | Text on accent backgrounds |
--muted | theme-muted-light | theme-muted-dark | Disabled states, subtle backgrounds |
--muted-foreground | theme-muted-foreground-light | theme-muted-foreground-dark | Secondary/dimmed text |
--background | theme-background-light | theme-background-dark | Page background |
--background-foreground | theme-background-foreground-light | theme-background-foreground-dark | Default body text |
--card | theme-card-light | theme-card-dark | Card/panel surfaces |
--card-foreground | theme-card-foreground-light | theme-card-foreground-dark | Text on cards |
All values must be 6-digit hex (#1e40af), no alpha channel. --primary-light is also reused for --bprogress-color (the top-of-page loading bar), so your brand color shows up there too without a separate setting.
Mode & Typography
| Setting key | Description |
|---|---|
theme-mode | System default: light, dark, or system. Individual users can override this locally without changing it for anyone else |
theme-radius | Border radius in rem applied via CSS variable, affects buttons/cards/inputs globally |
theme-font | CSS font-family stack (default ui-sans-serif, system-ui, sans-serif) |
theme-text-size | Global text scale factor |
Branding
| Setting key | Description |
|---|---|
image-url | Logo shown in the admin shell |
icon-url | Favicon |
system-name | Application name shown in the browser title and sidebar |
system-slogan | Tagline shown alongside the name |
menu-width | Sidebar width in rem |
These are consumed directly by the admin shell/layout components rather than ThemeProvider's CSS-injection logic — uploading new files for image-url/icon-url works the same way as any other file upload (see File Storage): upload first, then paste the resulting URL into the setting.
Applying Changes
Saving any setting in Settings → Configurations → Appearance takes effect on the next page load for that browser, since ThemeProvider reads from localStorage-cached settings and re-applies them via a useEffect on mount, not via a live socket push. If a teammate doesn't see your change, tell them to hard-refresh rather than assuming the setting didn't save.
Verify It Worked
- Hard-refresh the admin after saving a color and confirm the new color actually renders — not just that the setting saved successfully
- Toggle dark mode and confirm the
-darkvariants show up too, instead of falling back to Tailwind's defaults
Troubleshooting
| Symptom | Likely cause |
|---|---|
| A color setting has no visible effect | Value isn't a valid #rrggbb hex string — the rule is silently dropped, not an error |
| Dark mode looks unbranded even though light mode is styled correctly | You only set the -light variant of that color — set its -dark counterpart explicitly |
| User can't get out of a stuck dark/light preference | Their local override lives in localStorage under theme (and theme-source) on their own browser — clearing site data resets it to the system default |
| Logo/favicon doesn't update after uploading a new file | The setting still points at the old file's URL — re-paste the new uploaded URL into image-url/icon-url |