Theme customizer
A drawer over the app that writes config values live, and hands you the result as code.
Opening it
The floating trigger, ThemeCustomizerTrigger, sits on the trailing edge of the window, vertically centered. It moves to the opposite edge when ui.sidebarPosition puts the sidebar on its side, so the two never sit on top of each other, and it is marked sa-no-print.
From the keyboard, t then c. In code:
import { useCustomizer } from '@shared/composables/useCustomizer'
const { open, openCustomizer, closeCustomizer, toggleCustomizer } = useCustomizer()The state is a module-scope singleton. The drawer is mounted once in App.vue but the things that open it are scattered, so a shared ref means any of them can toggle it without threading state through the layout tree.
What it writes
Every control writes a config value and nothing else. The wiring turns that into a rendered change, which is why everything previews live and survives a reload.
| Section | Control | Writes |
|---|---|---|
| Presets | Six preset cards | The preset's partial config: three colors, a radius and a density. |
| Colors | Primary, secondary, accent | theme.primary, theme.secondary, theme.accent. |
| Dark mode | Switch | The color mode, stored separately under sa-color-mode. |
| Right to left | Switch | ui.direction. |
| Animations | Switch | ui.animations. |
| Layout | Four cards | ui.layout. |
| Sidebar position | Left or right | ui.sidebarPosition. |
| Density | Three buttons | ui.density. |
| Border radius | Four buttons | ui.borderRadius. |
| Typography scale | Three buttons | typographyScale. |
| Fonts | Primary, Arabic, monospace | fonts.primary, fonts.arabic, fonts.mono. |
| Developer | Copy config, Reset | Nothing, and everything. |
Copy config
Copy config puts the whole live config on the clipboard as a complete export const defaultConfig: SmartAdminConfig = { … } declaration, ready to replace the export in src/core/config/defaultConfig.ts. A short header above it says where to paste it.
It is TypeScript rather than JSON — single quotes, bare keys, trailing commas — so it survives the repository's lint rules without an edit.
Color mode is not a key in it, because it is not part of SmartAdminConfig. It lives under localStorage['sa-color-mode'], and the header names the mode you were in so the information is not silently lost.
Reset
Reset returns everything to defaultConfig. It asks first, through the app's own confirm dialog, and it is the same question and the same wording as the reset on the settings page — one gesture, two doors.
Reset does not clear the color mode, which is stored separately.
The settings page
/settings is the full-page version of the same controls, with more room and a few extra tabs. Both write to the same store, so a change in one is visible in the other immediately.
Shipping what you picked
- Dial the look in through the drawer.
- Copy config.
- Paste it over
defaultConfiginsrc/core/config/defaultConfig.ts. - If you were in dark mode and want that as the start, set
sa-color-modeyourself; the snippet's comment names it.
A reader's own saved config wins over a new default, because theirs is already in localStorage under sa-config. Rename that key when you need everyone on a new baseline.
Where to go next
- Global config — every key the drawer writes.
- Theming — what each of those keys does.