Theme Builder and Quasar color field
The Theme Builder centralizes the visual identity of forms edited with FormBuilder and rendered with FormViewer.
It replaces scattered styles with a serializable design-token contract covering palette, typography, spacing, radii, control variant, density, background, depth, motion, and iconography.
Open the Theme Builder
The Theme button in the top toolbar opens the side panel. Five presets are included:
- Enterprise;
- Ocean;
- Slate;
- Emerald;
- Sunset.
Any manual change switches the theme to custom. The panel preview updates immediately.
The Typography tab uses a dedicated responsive grid: base size and label weight remain aligned in two columns when space allows, then collapse to one column. Line height displays its value in a fixed badge so slider labels cannot overlap the preceding controls.
Advanced customization in 0.1.12
The panel adds two domains:
- Effects: neutral background, linear or radial gradient, angle, border, elevation, backdrop blur, and entry motion;
- Icons: Quasar or Iconify provider, primary, secondary and destructive actions, submit, previous and next glyphs, plus icon size.
The preview immediately replays fade, slide-up, scale, and soft-pulse. The system prefers-reduced-motion preference automatically disables them.
UnoCSS acts as the Theme Builder utility layer: presetWind3 provides visual shortcuts and UI animations, while presetIcons renders Iconify glyphs. The MDI Iconify collection is bundled locally through @iconify-json/mdi; runtime rendering does not require a remote API.
Icon selection uses QIconPicker. It only receives the layer-owned curated catalog, with search, pagination, keyboard access, and Quasar QTooltip support. The dependency is intentionally pinned to stable 2.0.7, compatible with Vue 3 / Quasar 2; its v2 contract (model-pagination, itemsPerPage, and the icon slot) is protected by project static checks.
Control the theme from the host application
<script setup lang="ts">
import type { QFormBuilderThemeConfig } from '@vevedh/qform-builder-layer/types'
import { createQFormBuilderTheme } from '@vevedh/qform-builder-layer/theme'
const schema = ref([])
const settings = ref({
preview: { width: 768, isFullWidth: false },
previewMode: 'editing' as const,
columns: 'sm' as const,
theme: createQFormBuilderTheme('enterprise'),
})
const theme = ref<QFormBuilderThemeConfig>(settings.value.theme)
const themeOpen = ref(false)
</script>
<template>
<FormBuilder
v-model:schema="schema"
v-model:settings="settings"
v-model:theme="theme"
v-model:theme-open="themeOpen"
/>
</template>The theme is also included in settings and in the save() payload. v-model:theme is a direct model for applications that persist or edit the theme separately.
Render the same theme with FormViewer
<FormViewer
v-model="values"
:form-fields="schema"
:theme="settings.theme"
/>Builder and viewer use the same normalizer and CSS variables, so a saved form keeps its visual identity outside the administration page that created it.
Public API
import {
applyQFormBuilderThemePreset,
createQFormBuilderTheme,
mergeQFormBuilderTheme,
normalizeQFormBuilderTheme,
qFormBuilderThemeToCssVariables,
} from '@vevedh/qform-builder-layer/theme'Methods exposed by the FormBuilder reference:
builderRef.value?.setTheme({
preset: 'custom',
palette: { primary: '#0057b8' },
})
const currentTheme = builderRef.value?.getTheme()
builderRef.value?.openTheme()
builderRef.value?.closeTheme()Design tokens
QFormBuilderThemeConfig covers palette, typography, spacing, radii, input variant, density, and container shadow. Tokens are scoped under .qform-theme; they do not globally overwrite the host application's Quasar palette.
New q-color field
The Community catalog no longer relies on a browser-dependent QInput type="color". The new FormKit q-color adapter combines:
- a controlled text input;
- a color swatch;
- a Quasar
QColorpopup; - HEX, HEXA, RGB, and RGBA formats;
- spectrum, tune, and palette views;
- a configurable palette;
- optional header and footer;
- a clearable value.
const colorField = {
$formkit: 'q-color',
name: 'brandColor',
label: 'Primary color',
value: '#2563eb',
formatModel: 'hexa',
defaultView: 'spectrum',
palette: ['#2563eb', '#0f766e', '#7c3aed'],
clearable: true,
}Existing schemas that use $formkit: 'q-input' with inputType: 'color' remain renderable for backward compatibility. They can be migrated progressively to q-color to access the advanced options.
Validation and security
The q-color field deliberately follows QColor's native contract: HEX, HEXA, RGB, and RGBA. Theme Builder design tokens use a separate CSS normalizer that also accepts HSL and HSLA. Active values such as url(...), concatenated CSS declarations, and unsafe font-family strings are rejected.
This separation prevents unsupported values from being forwarded to QColor while preserving flexible CSS tokens. It protects builder rendering; themes received from a client must still be validated by the Feathers/NFZ service before persistence.
Quasar model compatibility
Layer adapters normalize models before forwarding them to Quasar components:
q-colorreceives only a QColor-compatible string ornull;- the color text editor explicitly uses a text input;
q-timeandq-datetimeconvert missing FormKit values (undefinedor an empty string) tonull;- schema attributes such as
format24h,withSeconds, andnowBtnare converted to real booleans; - picker-only attributes are not leaked to the internal HTML control.
These rules prevent browser “value cannot be parsed” messages and Vue warnings from the QTime model validator.
Demo
The playground provides:
/themeAutomatic light/dark consistency
The Theme Builder now separates the serialized theme from the effective rendered theme:
- saved brand and status colors remain unchanged;
- when Quasar enables dark mode, the layer derives only the surfaces, text colors and borders required for contrast;
- neither the schema nor the persisted theme is mutated.
Reference dark palette:
{
surface: '#0f172a',
surfaceMuted: '#1e293b',
text: '#f8fafc',
textMuted: '#cbd5e1',
border: '#475569',
}Public helpers accept an optional color-mode argument:
import {
qFormBuilderThemeInputProps,
qFormBuilderThemeToCssVariables,
resolveQFormBuilderThemeForColorMode,
} from '@vevedh/qform-builder-layer/theme'
const effectiveTheme = resolveQFormBuilderThemeForColorMode(savedTheme, {
dark: true,
})
const style = qFormBuilderThemeToCssVariables(savedTheme, {
dark: true,
})
const inputProps = qFormBuilderThemeInputProps(savedTheme, {
dark: true,
})FormBuilder, FormViewer, and the Theme Builder preview automatically follow useQuasar().dark.isActive; the host application only needs to manage Quasar dark mode.
Covered components
The .qform-theme scope normalizes:
QInput,QSelect,QFile,QColor, and date/time pickers;QEditor, its toolbar, and content area;- checkboxes, radios, toggles, option groups, and button toggles;
- sliders, ranges, ratings, tabs, and steppers;
- sections, fieldsets, columns, editing overlays, and nested fields;
- quotes, information/warning banners, and code blocks, including legacy schemas.
Static content receives semantic rendering classes without mutating source data, so forms saved with historical bg-*-1 / text-*-9 classes remain readable.
Visual audit
The playground provides:
/dark-mode-auditAssociated commands:
bun run dark-mode:check
bun run dark-mode:runtime:check
bun run e2e:visual:update
bun run e2e:visualSnapshots protect light preview, dark preview, and dark editing mode. New component surfaces must consume --qfb-surface, --qfb-surface-muted, --qfb-text, --qfb-text-muted, and --qfb-border instead of hard-coded light colors.