Build dynamic, schema-driven forms across React, Vue, Angular, and Vanilla JS — fully decoupling data, UI, and logic.
Formitiva is a framework-agnostic runtime form engine for building forms from JSON schemas. The same definition can be rendered in React, Vue, Angular, or Vanilla JS without rewriting the form UI or business logic.
Formitiva is perfect for:
- Dynamic, backend-driven forms
- Low-code or visual form editors
- Reusable form components across multiple projects
- Enterprise-grade applications requiring extensible, maintainable form workflows
Traditional form development often mixes data, UI, and logic, which makes forms harder to maintain and extend. Formitiva separates these concerns:
- Schema (Data): Form structure and metadata
- Registry (UI + Logic Mapping): Connects schema fields to UI components and behaviors
- Renderer (Framework UI): Dynamically renders forms in React, Vue, Angular, or Vanilla JS
This architecture allows you to:
- Dynamically generate forms at runtime
- Swap or extend UI components easily
- Add conditional logic, validation, and plugins without touching schemas
- Keep forms maintainable across projects and teams
- Framework-Agnostic: Supports React, Vue, Angular, and Vanilla JS
- Dynamic Validation & Conditional Logic: Fields and sections can adapt in real-time
- Plugin System & Extensibility: Add custom components, field types, or validation rules
- Runtime Configurable: Forms can be sourced from APIs, databases, CMS, or code
- Decoupled Architecture: Separates schema, UI, and logic for scalability and maintainability
Formitiva is built around three decoupled layers:
Schema (Data)
↓
Registry (UI + Logic Mapping)
↓
Renderer (Framework UI)
| Layer | Responsibility |
|---|---|
| Schema | Defines form structure and metadata |
| Registry | Maps schema field types to components and logic |
| Renderer | Dynamically renders the UI |
This separation keeps the system flexible, extensible, and maintainable.
- Schemas can come from APIs
- UI components can be swapped
- Logic can be extended via plugins
Defines the structure of the form. Contains data only — no UI code.
{
"name": "country",
"type": "dropdown",
"options": [
{ "label": "USA", "value": "US" }
]
}Maps schema field types to UI components and logic. Decouples the schema from the UI implementation.
registerComponent("email", EmailField);Reads the schema, resolves components through the registry, and produces the final UI output.
Formitiva supports advanced page layouts without putting layout decisions inside the JSON schema. The schema should describe the form data and fields. The application decides how those fields are grouped and navigated by passing a layout config to the framework adapter.
This keeps one schema reusable across different screens:
- Render the same schema as a single long form on mobile.
- Render it as tabs in a settings page.
- Render it as a wizard during onboarding.
- Change layout per application without changing backend schema data.
Layouts are a thin rendering layer around the normal field renderer:
- The renderer loads and validates the schema as usual.
- If a
layoutconfig is passed, the built-in layout adapter becomes active. - The adapter tracks the active section.
- Each section lists field names in
props. - Only fields belonging to the active section are rendered.
- The same validation, visibility, computed values, translation, theme, and submit logic still apply.
definition.layoutRef and registerLayout() remain available as a compatibility fallback, but new code should prefer the explicit layout prop/input/option.
type LayoutConfig = {
name: string;
type: "nav" | "tab" | "wizard";
displayName: string;
defaultValue: string;
sections: Array<{
label: string;
name: string;
props: string[];
}>;
};Example:
const definition = {
name: "profile",
version: "1.0.0",
displayName: "Profile",
properties: [
{ name: "firstName", type: "text", displayName: "First Name", required: true },
{ name: "lastName", type: "text", displayName: "Last Name", required: true },
{ name: "email", type: "email", displayName: "Email", required: true },
{ name: "newsletter", type: "switch", displayName: "Newsletter" }
]
};
const tabLayout = {
name: "profileTabs",
type: "tab" as const,
displayName: "Profile Tabs",
defaultValue: "personal",
sections: [
{ label: "Personal", name: "personal", props: ["firstName", "lastName"] },
{ label: "Contact", name: "contact", props: ["email", "newsletter"] }
]
};React:
<Formitiva definitionData={definition} layout={tabLayout} />Vue:
<Formitiva :definition-data="definition" :layout="tabLayout" />Angular:
<fv-formitiva [definitionData]="definition" [layout]="tabLayout"></fv-formitiva>Vanilla:
const form = new Formitiva({
definitionData: definition,
layout: tabLayout
});Use type: "nav" for a left-side section menu, type: "tab" for tab buttons above the form, and type: "wizard" for step-by-step navigation. Wizard layouts disable the Next button while the current step has validation errors.
| Package | Framework | npm |
|---|---|---|
@formitiva/core |
Framework-agnostic | |
@formitiva/react |
React ≥ 17 | |
@formitiva/vue |
Vue ≥ 3.3 | |
@formitiva/angular |
Angular ≥ 18 | |
@formitiva/vanilla |
Vanilla JS / any |
Framework packages re-export the shared core APIs, so you usually only install the framework package you use. Built-in theme CSS files are separate assets and should be imported from @formitiva/core/themes/*, for example:
import '@formitiva/core/themes/material-dark.css';npm install @formitiva/reactPeer dependencies: react ^17 || ^18 || ^19, react-dom ^17 || ^18 || ^19
import { Formitiva } from "@formitiva/react";
const definition = {
name: "contactForm",
version: "1.0.0",
displayName: "Contact Form",
properties: [
{ name: "fullName", type: "text", required: true },
{ name: "email", type: "email", required: true }
]
};
const handleSubmit = (_def, _instanceName, values) => {
console.log('Form submitted with values:', values);
return undefined;
};
export default function App() {
return <Formitiva definitionData={definition} onSubmit={handleSubmit} />;
}npm install @formitiva/vuePeer dependencies: vue ^3.3
<script setup lang="ts">
import { Formitiva } from "@formitiva/vue";
const definition = {
name: "contactForm",
version: "1.0.0",
displayName: "Contact Form",
properties: [
{ name: "fullName", type: "text", required: true },
{ name: "email", type: "email", required: true }
]
};
const handleSubmit = (_def, _instanceName, values) => {
console.log('Form submitted with values:', values);
return undefined;
};
</script>
<template>
<Formitiva :definition-data="definition" :on-submit="handleSubmit" />
</template>npm install @formitiva/angularPeer dependencies: @angular/core >=18, @angular/common >=18, @angular/forms >=18, rxjs >=7
import { Component } from "@angular/core";
import { FormitivaComponent } from "@formitiva/angular";
const definition = {
name: "contactForm",
version: "1.0.0",
displayName: "Contact Form",
properties: [
{ name: "fullName", type: "text", required: true },
{ name: "email", type: "email", required: true }
]
};
@Component({
selector: "app-home",
standalone: true,
imports: [FormitivaComponent],
template: `
<fv-formitiva
[definitionData]="definition"
[onSubmit]="handleSubmit"
theme="material-dark"
></fv-formitiva>
`,
})
export class AppComponent {
definition = definition;
handleSubmit = (_def, _instanceName, values) => {
console.log('Form submitted with values:', values);
return undefined;
};
}npm install @formitiva/vanillaNo framework peer dependencies required.
import { Formitiva } from "@formitiva/vanilla";
const definition = {
name: "contactForm",
version: "1.0.0",
displayName: "Contact Form",
properties: [
{ name: "fullName", type: "text", required: true },
{ name: "email", type: "email", required: true }
]
};
const container = document.getElementById("form-root")!; // Suppose form-root is the parent node
const form = new Formitiva({
definitionData: definition,
onSubmit: (_def, _instanceName, values) => {
console.log('Form submitted with values:', values);
return undefined; // no errors
},
});
(async () => {
await form.mount(container);
})();Fields can be dynamically shown or hidden based on other field values — works identically across all frameworks.
{
"name": "state",
"type": "dropdown",
"parents": {
"country": ["US"]
}
}The state field is only displayed when country equals "US".
Supported validation modes:
onEdit— validate as the user typesonBlur— validate when leaving a fieldonSubmission— validate on form submit
Validation types:
- Field-level validation
- Form-level validation
- Built-in type validators
- Custom validators via the registry
Direct callback — works like other form libraries:
<Formitiva definitionData={definition} onSubmit={submitFunction} />Registered handlers — fully decouples submission logic from the form UI:
registerSubmitter("api:saveForm", async (data) => {
await fetch("/api/form", { method: "POST", body: JSON.stringify(data) });
});Referenced in schema:
{
"submitterRef": "api:saveForm"
}Formitiva provides a drag-and-drop visual builder for creating schemas.
Capabilities:
- Visually build forms
- Configure validation and conditional logic
- Preview instantly
- Export production-ready schemas
👉 https://formitiva.com/form-builder
- Node.js ≥ 18
- pnpm ≥ 9 (
npm install -g pnpm)
pnpm installBuild all packages at once (respects dependency order via Turborepo):
pnpm buildBuild individual packages:
pnpm build:core # @formitiva/core
pnpm build:react # @formitiva/react
pnpm build:vue # @formitiva/vue
pnpm build:angular # @formitiva/angular
pnpm build:vanilla # @formitiva/vanillapnpm testpnpm lintEach example app depends on the workspace packages, so build the packages first.
# React example (Vite)
pnpm --filter formitiva-react-demo build
# Vue example (Vite)
pnpm --filter formitiva-vue-demo build
# Angular example (Angular CLI)
pnpm --filter formitiva-angular-demo buildPreview a built example locally:
pnpm --filter formitiva-react-demo preview
pnpm --filter formitiva-vue-demo previewStart an example in development mode (packages are rebuilt on change via pnpm dev):
pnpm devPublish:
pnpm publish --filter "@formitiva/*" -rFormitiva was originally built with a strong focus on the React renderer. Support for Vue, Angular, and Vanilla JS is available, but these adapters would benefit from deeper ecosystem expertise and ongoing maintenance.
We are looking for contributors experienced with:
- Vue
- Angular
- Vanilla JavaScript
- Ensuring idiomatic patterns for each framework
- Fixing framework-specific bugs and edge cases
- Improving performance and reactivity handling
- Keeping feature parity across all renderers
- Enhancing documentation and examples
- Pick up issues labeled by framework (vue, angular, vanilla)
- Open discussions for design improvements
- Submit pull requests for fixes or enhancements
For development setup and architecture details, see DEVELOPMENT.md.
https://formitiva.com https://formitiva.com/docs
See DEVELOPMENT.md