Skip to content

useConfig ​

The store that owns the whole SmartAdminConfig. Everything the app lets a reader change lives here, and it survives a reload.

Import ​

ts
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:

ts
const cfg = useConfig()

cfg.setUi('layout', 'horizontal')

Members ​

MemberType on the instanceDescription
configSmartAdminConfigThe live config. Read it in a template and it re-renders on every change.
mergeConfig(partial: DeepPartial<SmartAdminConfig>) => voidDeep-merges a fragment. Arrays are replaced, not concatenated.
resetConfig() => voidBack to defaultConfig, as a structuredClone.
setFeature(key: keyof SmartAdminConfig['features'], val: boolean) => voidOne feature flag.
setUi<K extends keyof SmartAdminConfig['ui']>(key: K, val: SmartAdminConfig['ui'][K]) => voidOne 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:

ts
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:

ts
import { storeToRefs } from 'pinia'

const { config } = storeToRefs(useConfig())

The action functions are safe to destructure — they are bound to the store.

Example ​

vue
<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-persistedstate to localStorage under the key sa-config.
  • resetConfig clones defaultConfig rather 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. localStorage is 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.