@voicenter-team/form-renderer
v2.0.12
Published
--- title: Getting started description: Vue 3 + TypeScript library for rendering dynamic, translatable forms — editable (FormRenderer) and read-only (FormViewer). navigation: title: Getting Started ---
Keywords
Readme
title: Getting started description: Vue 3 + TypeScript library for rendering dynamic, translatable forms — editable (FormRenderer) and read-only (FormViewer). navigation: title: Getting Started
Form Renderer
A Vue 3.x + TypeScript library that renders dynamic forms from a JSON-like definition. It provides:
FormRenderer— an editable form (inputs, validation, conditional visibility, soft defaults).FormViewer— a read-only "summary / review" of the same definition: translated labels and categories with each field's value rendered as nicely formatted text.- Translations — a language dictionary with graceful fallback to schema literals, plus an optional per-language config (e.g. date-fns locales).
- Extensibility — register your own input components and read-only viewers.
Prerequisites
Install the peer dependency @voicenter-team/voicenter-ui-plus (^3.0.9) and vue (^3.5).
Installation
npm i @voicenter-team/form-renderer
# or
yarn add @voicenter-team/form-rendererQuick start — editable form
<template>
<FormRenderer
v-model="model"
:parameter-categories="categories"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { FormRenderer } from '@voicenter-team/form-renderer'
import type { TParametersCategory } from '@voicenter-team/form-renderer'
const model = ref<Record<string, unknown>>({})
const categories: Array<TParametersCategory> = [
{
uid: 1,
name: 'Category 1',
categoryData: { _uid: 'cat-1', styling: { columns: 6, gap: '1rem' } },
componentParameters: [
{
uid: 1,
name: 'NameInput',
description: '',
type: 'String',
propJpath: '$.name',
parameterData: {
_uid: 'name',
icon: 'vc-icon-answer',
title: 'Title',
subtitle: 'Subtitle',
helpText: 'Help Text',
mandatory: true,
defaultValue: '',
validationRegex: '\\S',
validationMessage: 'The field is required!',
parameterStyling: { gridColumnSpan: 3 }
}
}
]
}
]
</script>Read-only summary — FormViewer
FormViewer renders the same definition read-only. Bind it to a model (it never mutates it) and it
shows translated category headings, translated field labels, and each value as formatted text
(select option labels, Yes/No, dates, durations, key/value tables, etc.). Display-only blocks such as
InfoText are reused as-is.
<template>
<FormViewer
:parameter-categories="categories"
:model-value="model"
:translations="translations"
:active-language="activeLanguage"
:default-language="defaultLanguage"
empty-behavior="placeholder"
layout="grid"
/>
</template>Effective value & empty handling
A field shows its effective value: the saved model value, falling back to
parameterData.defaultValue when nothing is saved (this is how the form is actually configured).
When neither exists, the field is empty and emptyBehavior decides what happens:
| emptyBehavior | Behavior |
| --- | --- |
| 'placeholder' (default) | render the label + emptyPlaceholder (default —) |
| 'omit' | drop the field entirely (including its label and grid cell) |
Layout
By default FormViewer mirrors the form's grid (each category's categoryData.styling and per-field
parameterStyling). You can override it from above:
| Prop | Type | Notes |
| --- | --- | --- |
| layout | 'grid' \| 'flex' | default 'grid' |
| columns | number | global column override (grid) |
| gap | string \| number | global gap override |
| categoryLayout | Record<categoryUid, { layout?; columns?; gap? }> | per-category overrides |
Precedence (most specific wins): categoryLayout[uid] → top-level prop → categoryData.styling → default.
Slots
Both components expose named slots so you can inject your own wrappers (e.g. custom layout):
#[parameter-category-<uid>]="{ category }"— replace a category's rendering.#[parameter-<uid>]="{ parameter }"— replace a single field's rendering.
The slot-name prefixes are exported as enums (PARAMETER_CATEGORY_SLOT_PREFIX, PARAMETER_SLOT_PREFIX),
and the building blocks FormViewerCategory / FormViewerField are exported for use inside slots.
Form definition
A form is an array of categories, each containing parameters:
- Parameter —
{ uid, name, description, type, propJpath, parameterData }.typeselects the component,propJpath($.path.to.value) is where the value lives in the model, andparameterDataholds component-specific config (title,helpText,mandatory,defaultValue,parameterStyling,parameterConditions,_uid, …). - Display-only parameter — e.g.
InfoText; has nopropJpath/value, renders text only. - Category —
{ uid, name, icon?, showWhenEmpty?, componentParameters, categoryData }, wherecategoryData.stylingcontrols the grid (columns,gap).
Built-in component types
String, NSelect, YNSelect, PNInput, LangSelect, AotSelect, DTSelect, ISelect,
MISelect, IDataSelect, IFileUpload, MDEditor, ListInput, KeyValueInput, SUPrompt,
LocalizableString, LocalizableText, and the display-only InfoText.
Conditional visibility
Add parameterData.parameterConditions to show a field only when conditions are met. The same rules
apply in both FormRenderer and FormViewer:
parameterConditions: [
{ parameterConditionList: [
{ parameterConditionJPath: '$.isDeveloper', parameterConditionOperator: '=', parameterConditionValue: false }
] }
]Translations (i18n)
Pass a translations dictionary plus activeLanguage / defaultLanguage. Resolution falls back
active → default → the schema literal, so translations are always optional.
const translations = {
1: { 'category.cat-1.name': 'Personal details', 'param.name.title': 'Full name' },
2: { 'category.cat-1.name': 'פרטים אישיים', 'param.name.title': 'שם מלא' }
}Keys are built from the stable _uid (falling back to the numeric uid):
| Target | Key |
| --- | --- |
| Category name | category.<categoryData._uid>.name |
| Field label | param.<parameterData._uid>.title |
| Help text / placeholder / validation message / subtitle | param.<_uid>.helpText etc. |
| InfoText content | param.<_uid>.content |
| Select option label (ISelect / MISelect / IDataSelect) | param.<_uid>.opt.<optionKey> |
Select option labels are translated per option. The <optionKey> segment is the option's stable
_uid when present (assigned to new options by the IDataSelect editor); it falls back to the option's
value for legacy/DB options authored without a _uid, and finally to the array index. Options carry
a single translatable field (the label), so the key has no trailing field segment. Use
buildOptionLabelKey(parameterUid, option, index) to compose the key and the useOptionLabels
composable to resolve labels in custom components/viewers.
languageConfig — per-language date-fns locale
languageConfig is an optional, type-safe map from each language key to per-language settings. It is
used to localize date/duration formatting (e.g. the AotSelect duration in FormViewer). Supply a
date-fns Locale object imported from date-fns/locale:
<script setup lang="ts">
import { enUS, he } from 'date-fns/locale'
import type { LanguageConfig } from '@voicenter-team/form-renderer'
// Generic over the translations dict: when `translations` is a literal/typed object, TypeScript
// requires `languageConfig` to cover every one of its language keys.
const languageConfig = {
1: { dateFnsLang: enUS },
2: { dateFnsLang: he }
}
</script>
<template>
<FormViewer :translations="translations" :language-config="languageConfig" ... />
</template>LanguageConfigData is intentionally extensible — { dateFnsLang: Locale } today, add more
per-language settings later. The resolved active-language config is provided down the tree under
LANGUAGE_CONFIG_KEY, so custom viewers can read it too.
Custom components & viewers
Register custom input components for FormRenderer via customComponents, and custom
read-only components for FormViewer via customViewers (same shape, keyed by type):
<FormRenderer :custom-components="{ MyType: MyInput }" ... />
<FormViewer :custom-viewers="{ MyType: MyValueView }" ... />If a field's type has no registered viewer, FormViewer falls back to a generic FallbackViewer
that best-effort formats the value (so a value is always shown, never blank).
Exports
import {
FormRenderer, FormViewer, FormViewerCategory, FormViewerField,
FormParameterCategory, FormParameter, FormParameterConditionCheck,
dynamicComponents, dynamicViewers,
useResolvedText, useResolvedValue, useDynamicComponent, useFormViewerInject,
isDisplayParameter, isInputParameter, isDisplayType, registerDisplayType, isEmptyValue,
composeKey, helper, enums,
TRANSLATIONS_KEY, ACTIVE_LANG_KEY, DEFAULT_LANG_KEY, KEY_PATH_PREFIX, LANGUAGE_CONFIG_KEY
} from '@voicenter-team/form-renderer'
import type {
TParametersCategory, TParameter, ParameterTypes,
TranslationsDict, LanguageConfig, LanguageConfigData,
TViewerLayout, TViewerEmptyBehavior, CategoryLayoutOverride,
ICustomComponents, ICustomViewers
} from '@voicenter-team/form-renderer'Documentation
Visit the documentation for detailed info.
