npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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 ---

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-renderer

Quick 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 }. type selects the component, propJpath ($.path.to.value) is where the value lives in the model, and parameterData holds component-specific config (title, helpText, mandatory, defaultValue, parameterStyling, parameterConditions, _uid, …).
  • Display-only parameter — e.g. InfoText; has no propJpath/value, renders text only.
  • Category{ uid, name, icon?, showWhenEmpty?, componentParameters, categoryData }, where categoryData.styling controls 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.