useConfig
The store that owns the whole SmartAdminConfig. Everything the app lets a reader change lives here, and it survives a reload.
Import
import { useConfig } from '@core/config/useConfig'Signature
It is a Pinia setup store, not a plain composable. Its id is sa-config, and it persists itself to localStorage under that same key.
Calling it returns the store instance, with the refs already unwrapped, so cfg.config is a SmartAdminConfig rather than a ref to one:
const cfg = useConfig()
cfg.setUi('layout', 'horizontal')Members
| Member | Type on the instance | Description |
|---|---|---|
config | SmartAdminConfig | The live config. Read it in a template and it re-renders on every change. |
mergeConfig | (partial: DeepPartial<SmartAdminConfig>) => void | Deep-merges a fragment. Arrays are replaced, not concatenated. |
resetConfig | () => void | Back to defaultConfig, as a structuredClone. |
setFeature | (key: keyof SmartAdminConfig['features'], val: boolean) => void | One feature flag. |
setUi | <K extends keyof SmartAdminConfig['ui']>(key: K, val: SmartAdminConfig['ui'][K]) => void | One ui key, typed so the value has to match the key. |
Do not destructure config
Destructuring the store loses reactivity, so the first of these goes stale while the second keeps up:
const { config } = useConfig()
const cfg = useConfig()mergeConfig and resetConfig both replace the config object rather than editing it in place. A destructured config keeps pointing at the object that was there when you destructured, so the value your template reads stops changing while everything else in the app moves on.
Keep the store instance, as the template's own code does. When you do want standalone refs, use storeToRefs, which follows the replacement:
import { storeToRefs } from 'pinia'
const { config } = storeToRefs(useConfig())The action functions are safe to destructure — they are bound to the store.
Example
<script setup lang="ts">
import { useConfig } from '@core/config/useConfig'
const cfg = useConfig()
</script>
<template>
<div class="space-y-4">
<p>Layout: {{ cfg.config.ui.layout }}</p>
<button type="button" @click="cfg.setUi('layout', 'horizontal')">
Horizontal layout
</button>
<label class="flex items-center gap-2">
<input
type="checkbox"
:checked="cfg.config.features.commandPalette"
@change="cfg.setFeature('commandPalette', ($event.target as HTMLInputElement).checked)"
>
Command palette
</label>
<button type="button" @click="cfg.resetConfig()">
Reset
</button>
</div>
</template>Notes
- It is a value store, and only a value store. It never calls a theme, font or direction setter.
setupConfigWiring()watches it and is the single path downward, which is why a config import and a reader's click end up in exactly the same state. Global config walks that wiring. - Persisted through
pinia-plugin-persistedstatetolocalStorageunder the keysa-config. resetConfigclonesdefaultConfigrather than assigning it, so a later mutation cannot leak into the module the rest of the app reads.- The store is not SSR-safe as it stands.
localStorageis named when the module is evaluated, so importing it in a server render throws. The template is a single-page app and never does; if you move it behind SSR, guard the persist option.
Related
- Global config — every key, and how it reaches the DOM.
usePageConfig— per-page overrides that do not touch the store.useTheme— the engine the color keys drive.- Theme customizer — the UI that writes to this store.