Configuration
Every app-wide setting lives in a single object passed to createVueform — registered components, Modules, and rules, theming, Locales, Defaults, and each Module's options. It's where a decision is made once instead of on every form — the theme, the base URL requests go to, the locale of error messages, a custom rule every form can use by name.
Passing Options
The config object is createVueform's single argument:
import { createVueform } from '@vueform/ui/bundle'
app.use(
createVueform({
darkMode: '.dark',
$http: { baseURL: 'https://api.example.com' },
})
)With the Full Bundle (@vueform/ui/bundle) everything is pre-registered — components, Modules, rules, Theme, Locale, adapters — so every option here is an optional tweak. On the bare @vueform/ui entry nothing is, and registering what your app uses happens right here in the config — Modular Setup walks through that wiring.
A config authored outside the call — in a shared file, say — keeps its typing with defineConfig.
The resolved object is available as vueform.config on the instance and through useConfig() inside components — see Vueform Instance.
In development the resolved config is frozen once installed, so writing to it after app.use() throws. Config changes belong in a Plugin's config hook, before install — see Plugins.
How Options Merge
The merge runs once, inside createVueform(). Your keys go over the entry's, then every Plugin's config hook runs on the result — Vueform Instance walks through the full sequence. Most options simply replace the entry's value, with these exceptions:
| Option | Combined with the entry's value |
|---|---|
components, modules, icons, build | Shallow-merged — your keys add to or override the entry's. |
every $-prefixed Module option | Shallow-merged per Module — $http: { baseURL } keeps the entry's adapter. |
plugins | Appended after the entry's own Plugins. |
globals | Both hooks run, the entry's first — see Globals. |
theme | Replaces the entry's Theme wholesale, dark block included. |
tokens | Merged on top of the resolved Theme — top-level keys replace, dark blocks shallow-merge. See Themes & Tokens. |
locale | Wrapped in a ref — a ref you pass is used as-is. |
| everything else | Replaced. |
The last row matters most for rules and locales — your object replaces the entry's built-ins instead of adding to them. To extend, spread the subpath export:
import { createVueform } from '@vueform/ui/bundle'
import * as rules from '@vueform/ui/rules'
import { phone } from './rules'
createVueform({
rules: { ...rules, phone },
})The same replacement applies to locales — registration and the active-Locale consequence are covered in Locales. breakpoints and matrixTypes replace too, so adding to them means re-listing the defaults (breakpoints, matrixTypes).
Registries
components
- Type:
Record<string, VueformComponent> - Default:
{}— the Full Bundle pre-registers every built-in
Every entry is registered globally with app.component() at install, and a schema type resolves to a component by that name — type: 'my-input' renders VfMyInput, so the registration key is the name. Your entries add new names or replace a built-in under the same name:
import { createVueform } from '@vueform/ui/bundle'
import { VfRating } from './vueform/components'
createVueform({
components: { VfRating }, // type: 'rating' now renders it
})Registration instead of auto-import is deliberate — schema types are strings resolved at runtime, invisible to any bundler, as Code-Splitting explains. Building and registering your own components is covered in Components.
modules
- Type:
VueformModules - Default:
{}— the Full Bundle registers every built-in
Module definitions created with defineModule, keyed by Module name. model is the one required entry on the bare entry:
import { createVueform } from '@vueform/ui'
import * as modules from '@vueform/ui/modules'
createVueform({
modules,
})Registering and leaving modules out is covered in Modules, and replacing one in In-Depth Modules.
rules
- Type:
Record<string, RuleDefinition> - Default:
{}— the Full Bundle registers the built-in catalog
Validation rules available to every form by name. Your object replaces the entry's catalog, so spread the built-ins when extending — see how options merge. Authoring rules with createRule and typing them through the CustomRules augmentation is covered in Validation.
plugins
- Type:
VueformPlugin[] - Default:
[]
Plugins applied at creation, in array order, after any Plugins the entry ships (the PrimeVue entry ships two):
import exprEval from '@vueform/expr-eval'
import dompurify from '@vueform/dompurify'
createVueform({
plugins: [exprEval(), dompurify()],
})See Plugins.
Component Props
defaults
- Type:
ComponentDefaultsMap - Default:
{}
Default prop values keyed by component name, with '*' for every component and '*element' for every element:
createVueform({
defaults: {
'*element': { columns: 6 },
VfText: { placeholder: 'Type here...' },
},
})Per component the layers stack '*' → '*element' → the component's own key, with classes merging and presets concatenating. See Defaults.
presets
- Type:
Record<string, PresetDefinition> - Default:
{}
Named prop bundles a component opts into through its presets prop — or that target components themselves via $apply:
createVueform({
presets: {
compact: {
$apply: '*element',
columns: 6,
},
},
})Targeting, cascading, and multi-rule presets are covered in Presets.
mergeObjectProps
- Type:
boolean - Default:
true(unset counts astrue)
When false, a plain-object prop you pass replaces the object coming from blueprint defaults, config defaults, and Presets instead of shallow-merging into it. Arrays and non-plain values always replace, and classes / presets keep their own merge semantics either way. See Defaults.
Theming
theme
- Type:
ThemeTokens - Default: the entry's bundled Theme (none on the bare entry)
The base token map. It replaces the entry's Theme wholesale, dark block included — on the bare entry it's how a Theme gets selected at all:
import { createVueform } from '@vueform/ui'
import { primary as theme } from '@vueform/ui/themes'
createVueform({
theme,
})Choosing and authoring themes is covered in Themes & Tokens.
tokens
- Type:
ThemeTokens - Default:
{}
Token overrides merged on top of the resolved base Theme when createVueform() runs — top-level keys replace, dark blocks shallow-merge:
createVueform({
tokens: {
brand: '#5b21b6',
gutter: '1.5rem',
dark: {
brand: '#a78bfa',
},
},
})See Themes & Tokens.
darkMode
- Type:
'system' | string - Default: unset, behaving as
'system'
How Dark Mode is detected globally:
'system'— follows the OS preference viaprefers-color-scheme: dark; the dark CSS block is emitted inside a@mediaquery.- A CSS subselector (
'.dark','[data-theme="dark"]', …) — Vueform watches<html>for that class or attribute and switches when it appears; the dark block is emitted as:root<subselector> { ... }.
createVueform({
darkMode: '.dark',
})build
- Type: an object, shallow-merged with the entry's
| Field | Default |
|---|---|
styleId | 'vueform-theme' |
scopedDarkMode | false |
darkClass | 'vf-dark' |
lightClass | 'vf-light' |
styleId is the id of the injected <style> tag and of getStyleTag()'s output. On the client an existing tag with that id is reused, so two instances on one page need distinct ids — development warns when the contents differ. See SSR for where the tag ends up in server-rendered apps.
scopedDarkMode additionally emits darkClass / lightClass blocks that re-declare every root Token, pinning any subtree wrapped in one of those classes to that mode:
createVueform({
build: {
styleId: 'my-vueform-theme',
scopedDarkMode: true,
},
})nonce
- Type:
string | undefined - Default:
undefined
A CSP nonce applied to every <style> tag Vueform injects and to getStyleTag()'s output. Coverage details live in Security, per-request nonces in SSR.
useHead
- Type:
boolean - Default:
true
When the app provides @unhead/vue, the generated stylesheet goes through it. Otherwise — or with false — a <style> tag is injected into <head> directly. See SSR.
Classes
reclass
- Type:
ComponentClassesMap - Default:
{}
Replaces a component's base classes per Part, keyed by component name, '*element', or '*':
createVueform({
reclass: {
VfText: { input: 'my-input' },
},
})Per Part the most specific entry wins — component name, then '*element', then '*' — the three are not merged. See Classes.
layoutClasses
- Type:
LayoutClassesMap - Default: unset
Per-Part overrides of the shared element classes the Element Layout installs at the bottom of the class chain (root, label, errors, …):
createVueform({
layoutClasses: {
root: 'my-element',
label: 'my-label',
},
})See Classes.
classHelpers
- Type:
boolean - Default:
false
Development-only markers on every resolved class value, for tracing which class key styles what. See Classes.
classHelper
- Type:
(key: string) => string - Default:
(key) => `[__${key}__]`
The function producing the helper markers. It receives ComponentName.classKey, and whitespace is stripped from its result:
createVueform({
classHelpers: true,
classHelper: (key) => `[${key}]`,
})See Classes.
Grid and Layout
breakpoints
- Type:
Record<string, string | number> - Default:
{ sm: '640px', md: '768px', lg: '1024px', xl: '1280px', '2xl': '1536px' }
The responsive breakpoints. Keys become the breakpoint names of columns props and of the generated vf-col-{breakpoint}-{size} classes, and numeric values are read as pixels. The map replaces the default whole, so adding one means re-listing the rest:
createVueform({
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
xxl: '1920px',
},
})How breakpoints drive column layout is covered in Form Structure.
mobileFirst
- Type:
boolean - Default:
true
Switches the generated media queries between min-width (mobile-first) and max-width (desktop-first). Global only — see Form Structure.
grid
- Type:
boolean - Default:
true
Whether Vueform generates the column CSS and the per-breakpoint label-alignment rules. With false you bring both yourself.
cols
- Type:
(size?: number, breakpoint?: string) => string - Default: emits
vf-col,vf-col-{size},vf-col-{breakpoint}, andvf-col-{breakpoint}-{size}
The column class name generator. It's called both when columns props resolve and when the grid CSS is generated — each class gets a grid-column: span … rule — so the two always agree. Mapping onto another framework's column classes therefore pairs with grid: false, since the generated CSS would otherwise target the foreign names:
createVueform({
grid: false,
cols: (size, breakpoint) =>
breakpoint ? `col-${breakpoint}-${size ?? 12}` : `col-${size ?? 12}`,
})label
- Type:
(size: number, breakpoint?: string) => string - Default: returns
vf-horizontalbelow12andvf-verticalat12, prefixed withvf-col-{breakpoint}:when scoped
The label-alignment class generator. The generated grid CSS aligns labels by selecting {cols(undefined, breakpoint)}:vf-horizontal / :vf-vertical, so a custom cols needs a label whose scoped output matches — or grid: false with your own alignment CSS. Written out, the default generator is:
createVueform({
label: (size, breakpoint) => {
const alignment = size < 12 ? 'vf-horizontal' : 'vf-vertical'
return breakpoint === undefined ? alignment : `vf-col-${breakpoint}:${alignment}`
},
})See Form Structure.
matrixTypes
- Type:
{ single: [string, string]; multiple: [string, string]; auto: [string, string] } - Default:
{ single: ['radio', 'radios'], multiple: ['checkbox', 'checkboxes'], auto: ['text', 'group'] }(the PrimeVue adapter ships its own)
The element types VfMatrix renders per selection mode — each tuple is the cell's element type and the row holder's type. Types resolve through the component registry, and a column's own schema.type wins over the mode's cell type, so custom elements can back any mode. The object replaces the default whole, and its type requires all three modes, so changing one means re-listing the rest:
createVueform({
matrixTypes: {
single: ['my-radio', 'my-radios'],
multiple: ['checkbox', 'checkboxes'],
auto: ['text', 'group'],
},
})See Form Structure.
Icons
icon
- Type:
(name: string) => string - Default:
(name) => name(the PrimeVue adapter prefixespi pi-)
Resolves every icon prop into the class string rendered for it:
createVueform({
icon: (name) => `fa-solid fa-${name}`,
})Per-element icon usage is covered in Element Layout.
icons
- Type:
Record<string, string> - Default:
{ add: 'vf-icon-add', remove: 'vf-icon-remove', sort: 'vf-icon-sort' }
Semantic icon names behind $-prefixed icon props. $add looks up icons.add (falling back to the bare name), and the result still runs through icon, so the two options compose:
createVueform({
icons: {
add: 'plus',
remove: 'trash',
sort: 'grip-vertical',
},
icon: (name) => `fa-solid fa-${name}`,
})With this pair $add renders fa-solid fa-plus. The map is shallow-merged with the entry's. Semantic icons appear in Dynamic Props and the Repeater controls.
Internationalization
The i18n Module reads these top-level keys, and each of them can be overridden per app through the $i18n options bag under Module Options.
locales
- Type:
LocalesProp - Default:
{}({ en_US }on the Full Bundle)
The registered locale definitions keyed by Locale code:
import { en_US, de_DE } from '@vueform/ui/locales'
createVueform({
locales: { en_US, de_DE },
locale: 'de_DE',
})Your map replaces the entry's — see Locales.
locale
- Type:
string | Ref<string> - Default:
''
The active Locale key. It's never required — when empty, the first registered key is active, however many are registered. A ref binds two-way. See Locales.
fallbackLocale
- Type:
string - Default:
''(no fallback)
The Locale consulted when a tag is missing from the active one. See Locales.
localeParamPattern
- Type:
RegExp - Default:
/\{(\w+)\}/g
The pattern matching parameter placeholders in Locale messages. The first capture group is the parameter name, and the pattern always runs globally:
createVueform({
localeParamPattern: /:(\w+)/g, // placeholders look like :name
})See Locales.
languages
- Type:
Record<string, string | Record<string, string>> - Default:
{}
The languages available to multilingual inputs — keys are the codes stored in the model, values the (localizable) labels:
createVueform({
languages: { en_US: 'English', fr_FR: 'French' },
})A form's own languages prop takes precedence. See Multilingual Inputs.
ruleMessageTag
- Type:
(name: string, type?: 'array' | 'string' | 'numeric' | 'file') => string - Default: emits
rule_{name}_error, orrule_{name}_{type}_errorfor typed rules
Maps a Validation rule to the Locale Tag of its error message. type is passed only for typed rules, and a tag missing from the Locale falls back to ruleMessageTag('default'):
createVueform({
ruleMessageTag: (name, type) =>
type ? `validation.${name}.${type}` : `validation.${name}`,
})See Validation.
Module Options
Each Module reads its options from the config key named after it with a $ prefix, and the bags are shallow-merged with the entry's. Configuring a bag whose Module isn't registered warns at createVueform() in development — the module list lives in Modules. $devtools, $model, $theme, and $validation exist but take no options yet.
$autosave
storage (DraftStorage) sets the default storage backend for Drafts, localStorage where available:
createVueform({
$autosave: { storage: sessionStorage },
})A form's own draft.storage wins. See Autosave.
$conditions
strict (default false) switches structured Conditions to strict comparisons — see Conditions:
createVueform({
$conditions: { strict: true },
})debounce (default 300) is the delay in milliseconds before async conditional props re-evaluate — see Conditions.
$date
adapter is the date backend the Date Module requires — it throws on first use without one, and the Full Bundle wires nativeDateAdapter:
createVueform({
$date: { adapter: myDateAdapter },
})Writing your own is covered in Custom Adapters.
$expression
| Field | Default | Description |
|---|---|---|
enabled | true | false switches the Expression system off entirely. |
whitelist | ['*'] | The prop keys that resolve Expressions — fail-closed when unset. See Restriction Levels. |
localConsts | true | Whether a form's consts prop counts. See Constants. |
functions | — | Custom Expression functions. See Custom Functions. |
consts | — | Global Expression constants. See Constants. |
debug | on in development | Warnings are logged unless set to false. See Debugging. |
adapter | pathExpressionAdapter on the Full Bundle | The Expression engine. Without one the Module stays inert with a dev warning. See expr-eval. |
createVueform({
$expression: {
whitelist: ['label', 'default'],
consts: { APP_NAME: 'Acme' },
},
})$http
The defaults every request picks up — Vueform's own and your useHttp() calls alike:
createVueform({
$http: {
baseURL: 'https://api.example.com',
headers: { 'X-App': 'myapp' },
timeout: 10000,
withCredentials: true,
},
})| Option | Type | Default | Description |
|---|---|---|---|
baseURL | string | — | Base URL prepended to relative request URLs. |
headers | Record<string, string> | {} | Headers sent with every request. |
timeout | number | 0 (none) | Request timeout in milliseconds. |
requestType | RequestType | 'json' | Body encoding. |
responseType | ResponseType | 'json' | How the response body is parsed. |
withCredentials | boolean | false | Include credentials in cross-origin requests. |
base64 | boolean | false | Convert File/Blob values to base64 in JSON bodies. |
defaultMethod | RequestMethod | 'GET' | Method when none is specified. |
onRequest | RequestInterceptor[] | — | App-wide request interceptors. |
onResponse | ResponseInterceptor[] | — | App-wide response interceptors. |
adapter | HttpAdapter | fetchHttpAdapter on the Full Bundle | The transport. Without one every request rejects. |
App-wide interceptors nest inside instance-registered ones — instance request → config request → adapter → config response → instance response — as HTTP details. Per-request config overrides these defaults, and headers merge — see HTTP.
$i18n
locales, locale, fallbackLocale, and localeParamPattern override the top-level keys of the same name, and adapter (I18nAdapter) replaces the whole i18n backend:
createVueform({
$i18n: { fallbackLocale: 'en_US' },
})See I18n.
$router
queryParam encodes the current step into a query parameter (a form's routerQueryParam prop overrides it), router passes a vue-router instance — or false to disable auto-detection — and adapter replaces the built-in vue-router / pushState integration:
createVueform({
$router: { queryParam: 'step' },
})The top-level router option is the same setting as $router.router, which wins when both are set; auto-detection reads the app's global $router. See Multi-Page Forms.
$security
| Field | Type | Default | Description |
|---|---|---|---|
sanitizer | (html: string) => string | — | The HTML sanitization engine. See HTML Sanitization. |
encrypt | (value, purpose) => string | Promise<string> | — | Encrypts values before features persist them. See Draft Encryption. |
decrypt | same as encrypt | — | The inverse, applied when persisted values are read back. |
allowOrigins | string[] | — | The origins requests may target — unset, any; set, the app's own origin plus the listed ones. See Request Origins. |
allowHtml | SecurityHtmlArea[] | — | The areas where strings may render as HTML — unset, all of them; set, only the listed ones. See HTML Sanitization. |
allowComponents | string[] | — | The registered component names a schema string may resolve to — unset, all of them; set, only the listed ones. See Component Names. |
allowProps | (string | RegExp)[] | — | The prop paths path@prop references may resolve — unset, all of them; set, only the matching ones. See Prop References. |
banAttrs | (string | RegExp)[] | — | Keys dropped from every schema-reachable prop bag — unset, none beyond the built-in invariants. Strings match case-insensitively, and every entry is checked against both the original and the camelized key. See Banning Attributes. |
What to configure when schemas come from untrusted sources is covered in Security:
import dompurify from '@vueform/dompurify'
createVueform({
plugins: [dompurify()],
$security: {
allowHtml: ['content'],
allowComponents: ['MyOption'],
},
})Runtime Behavior
globals
- Type:
(context: GlobalsHookContext) => Partial<VueformGlobals> | void
The hook extending the app's Globals. It receives { config, globals } and returns the members to add, running lazily the first time Globals are read — after the entry's own hook and before plugins' hooks:
import { ref } from 'vue'
createVueform({
globals: () => ({
currentUser: ref(null),
}),
})globalWarns
- Type:
boolean - Default:
true
Development warning when a globals hook overrides an existing member. See Globals.
uid
- Type:
() => string - Default:
() => useId()
Generates the id of every form and element. It's called during their setup(), so composables such as useId() and getCurrentInstance() work inside it:
import { useId } from 'vue'
createVueform({
uid: () => `order-${useId()}`,
})The server/client constraints on custom generators are covered in SSR.
lazy
- Type:
boolean - Default:
true
Element views mount at browser idle (requestIdleCallback, or a 300 ms timeout without it) or as soon as they become visible, while values, Validation, and Conditions register at mount regardless. false renders every element's DOM at mount — e.g. for DOM assertions in tests. See Performance and Testing.
nativeEmit
- Type:
boolean - Default:
false
Switches component events from Vueform's event system to native Vue emits. The trade-offs are covered in Event Handlers.
devtools
- Type:
boolean - Default:
true
false switches the @vueform/devtools inspector off even in development. See Model.