Architecture
Formspec is a JSON-native form specification where structure, behavior, and presentation are independent, composable documents. A single definition drives validation, computed fields, conditional logic, and repeatable sections across five runtimes — web, React, iOS, Android, and server.
Document layers
Every Formspec project is a set of composable document layers. Each layer adds a concern without modifying the layers below. Presentation layers (Theme, Components) never affect data collection, validation, or behavioral semantics — they are swappable overlays on an unchanging behavioral core.
The specification as abstraction boundary
Formspec inverts the usual dependency between frontend and backend.
The specification — JSON Schemas,
normative prose, and the
FEL grammar — is the stable
center that all implementations conform to. A Rust shared kernel provides
one implementation of all pure logic — FEL evaluation,
assembly, linting, mapping, and more — exposed via WASM to TypeScript (browser and React) and PyO3 to Python.
Native mobile renderers (iOS and Android) bridge to the same WASM kernel through a hidden WebView.
The TypeScript engine keeps Preact Signals for reactive UI state; the React package bridges those
signals to React via useSyncExternalStore. Submit a form on Android; re-validate it on the server with full
confidence in semantic equivalence — backed by a single Rust implementation across all five runtimes.
Rendering is two layers, not one
The layout planner sits between the engine and any renderer. It resolves the theme cascade, expands custom components, merges responsive breakpoints, and produces a plain LayoutNode tree — a JSON-serializable blueprint that says "put a TextInput here with these styles" without knowing anything about the DOM, React, SwiftUI, or Jetpack Compose.
Three renderers consume that blueprint today. The <formspec-render> web component
walks the LayoutNode tree, creates DOM elements, and wires Preact Signals for live reactivity.
The formspec-react React package does the same via hooks and useSyncExternalStore,
with an overridable component map for plugging in Shadcn, Material UI, or any React component library.
Native mobile renderers (formspec-swift and formspec-kotlin) bridge to
the engine through a hidden WebView and expose platform-native state (SwiftUI @Published,
Compose State<T>) — same engine, native components.
The engine owns data and logic. The planner owns presentation decisions. Renderers own pixels.
Headless adapter architecture
The web component uses a headless behavior/adapter split. Behavior hooks extract
reactive state from the engine and return a typed contract — ARIA attributes, keyboard navigation,
focus management, and validation announcements are all handled here. Render adapters
build the DOM structure and CSS classes for a specific design system, then call bind()
to wire everything up. This separation means every adapter inherits WCAG 2.1 AA and Section 508
compliance automatically — accessibility lives in the behavior layer, not the DOM layer.
A USWDS adapter ships out of the box for federal agency branding. The same
architecture supports Bootstrap, Tailwind, Material, or any custom design system — write a render
adapter, and the behavior hooks handle the rest. Missing component entries fall back to the default
adapter, so you can adopt incrementally. The React package uses a different approach —
an overridable component map where you replace individual field renderers
(e.g., components={{ fields: { TextInput: MyShadcnInput } }})
while keeping all behavior and accessibility logic intact.
Studio Core is an authoring adapter — every edit is a serializable command with undo/redo, replay, and cross-artifact diagnostics. CLI tools, LLM agents, and visual editors all drive Studio Core through the same command API.
FEL (Formspec Expression Language)
A small, deterministic expression language embedded in bind and shape declarations. FEL handles calculated fields, conditional visibility, cross-field constraints, and aggregation — no custom code required. Defined once as a formal grammar with 61 stdlib functions, implemented identically in both runtimes.
// Conditional visibility — show EIN field only for nonprofits "relevant": "$org_type = 'nonprofit'" // Calculated field — monthly budget from total and duration "calculate": "$amount / $duration" // Cross-field validation — budget must balance "constraint": "sum($line_items) <= $award_amount" // Aggregation with filtering "calculate": "countWhere($items, $status = 'approved')"
FEL was designed from the start to be JSON-native, deterministic, and side-effect-free.
Field references use $field_id syntax. Variables use @name.
The language is small enough to fit in a single grammar file, yet expressive enough to handle
multi-level budget calculations, conditional visibility chains, and cross-field constraint composition.
The Rust shared kernel provides a single FEL implementation
with base-10 decimal arithmetic (28-digit precision), compiled to WASM for the browser and PyO3 for the server.
Specifications & schemas
Structural truth lives in JSON Schemas. Behavioral semantics that schemas cannot encode live in the normative spec prose. Clean separation: schemas handle structure, specs handle behavior.
| Tier | Spec | Schema |
|---|---|---|
| Core | Core Spec, FEL Grammar | definition, response, validationReport, validationResult |
| Theme | Theme Spec | theme |
| Components | Component Spec | component |
| Mapping | Mapping DSL | mapping |
| Extensions | Extension Registry, Changelog | registry, changelog |
| Companions | References, Ontology, Locale, Assist | references, ontology, locale, assist |
| Catalogs | — | fel-functions, core-commands, conformance-suite |
TypeScript packages
| Package | Role | Description |
|---|---|---|
formspec-types | Core | Types auto-generated from JSON Schemas — zero-runtime, shared across all packages |
formspec-engine | Core | FormEngine, FEL pipeline, assembler, reactive signals |
formspec-layout | Rendering | Layout planner — renderer-agnostic blueprint from definition + theme + components. Consumed by formspec-webcomponent; available to any renderer (React, native, PDF). |
formspec-webcomponent | Rendering | <formspec-render> — built-in browser renderer, component registry, 34 built-in components |
formspec-react | Rendering | React hooks + auto-renderer. <FormspecForm> walks the LayoutNode tree; overridable component map for Shadcn, Material UI, or any React library. Exports formspec-react (full) and formspec-react/hooks (hooks only). |
formspec-adapters | Rendering | Design-system render adapters — USWDS ships built-in; adapter contract supports Bootstrap, Tailwind, Material, or custom systems |
formspec-core | Authoring | Project core — 17 handlers, normalization, page resolution, theme cascade |
formspec-studio-core | Authoring | Authoring core — command model, undo/redo, queries, diagnostics |
formspec-studio | Authoring | Visual form editor (React 19) — desktop-first authoring, inspector, logic builders |
formspec-mcp | Integration | MCP server — 28 consolidated tools for LLM-driven form authoring (stdio transport) |
formspec-assist | Integration | Form-filling agent protocol — context resolution, profile matching, WebMCP transport + shim. No LLM, no renderer. |
formspec-chat | Integration | Chat core — conversational form builder logic, AI adapter interface, issue queue |
formspec-assist-chat | Integration | Conversational form-filling — LLM-powered Q&A, guided walkthrough, proactive suggestions, document extraction. Consumes formspec-assist tools. |
Rust shared kernel
A set of Rust crates replacing duplicated logic across the TypeScript and Python implementations. One codebase, compiled to WASM (browser/Node) and PyO3 (Python). Read the full story.
| Crate | Description |
|---|---|
fel-core | FEL lexer, parser, evaluator (base-10 decimal), environment, extensions, dependency extraction, AST printer |
formspec-core | FEL analysis, path utils, schema validator, extension analysis, runtime mapping, assembler, registry client, changelog |
formspec-eval | Definition Evaluator — 4-phase batch processor with topo sort, inheritance, NRB, wildcards |
formspec-lint | 7-pass static linter — 35 diagnostic codes, pass gating, authoring/runtime modes |
formspec-wasm | WASM bindings (wasm-bindgen) — exposes all capabilities to TypeScript |
formspec-py | PyO3 bindings — exposes all capabilities to Python |
Python modules
The Python package is a plain library — import it into any backend framework. Most pure logic is migrating to the Rust kernel (via PyO3). What stays: format adapters (JSON, XML, CSV serialization) and the artifact orchestrator CLI.
| Module | Description |
|---|---|
fel/ | FEL parser, AST, evaluator, dependency extractor |
validator/ | Multi-pass static linter (~40 diagnostic codes across 9 validation passes) |
mapping/ | Bidirectional rule engine |
adapters/ | JSON, XML, CSV serializers |
evaluator.py | 4-phase form processor (rebuild → recalculate → revalidate → apply NRB) |
validate.py | Directory-level artifact validator (10-pass, auto-discovery) |
Native iOS and Android renderers
Native mobile packages render Formspec definitions using platform-native UI frameworks —
SwiftUI on iOS/macOS and Jetpack Compose on Android. Both use the same architecture:
a hidden WebView bridges to the Formspec engine (identical WASM kernel), while a thin
platform layer (~500 lines) converts engine signals into native state types
(@Published on iOS, State<T> / StateFlow<T> on Android).
This design keeps Rust as the shared logic layer while giving native developers the
experience they expect: Swift packages for iOS, Gradle/Maven for Android. Both packages
support "bring your own components" — override any field renderer with a custom
@Composable or SwiftUI View. No INTERNET permission is required
for basic form operation on Android; forms work fully offline on both platforms.
| Platform | Package | UI Framework | Engine Bridge | Status |
|---|---|---|---|---|
| Web (any) | formspec-webcomponent | Custom Elements | Direct (WASM in-process) | Shipping |
| Web (React) | formspec-react | React 18+ | Direct (WASM in-process) | Shipping |
| iOS / macOS | formspec-swift | SwiftUI | Hidden WKWebView | Shipping |
| Android | formspec-kotlin | Jetpack Compose 1.5+ | Hidden Android WebView | ADR accepted |
| Server | formspec (Python) | N/A | PyO3 native binding | Shipping |
Why separate native packages instead of Kotlin Multiplatform or Compose Multiplatform? Because the Rust crates already provide the shared logic layer. Platform binding code is thin. iOS developers expect Swift packages; Android developers expect Gradle/Maven. And Compose Multiplatform would kill the "bring your own components" story — the whole point is that a team using Material Design 3 on Android and a custom design system on iOS can override individual components without forking the package.
Worked examples
The examples directory includes complete worked examples that exercise different tiers and capabilities of the spec.
| Example | Description |
|---|---|
invoice | Line-item invoice with repeat groups and calculated totals |
clinical-intake | Healthcare intake form with screener routing and nested repeats |
grant-application | 6-page federal grant form — all tiers exercised |
grant-report | Grant reporting variants (tribal-long, tribal-short) |
refrences | Cross-reference dashboard — fields, binds, FEL, shapes |
Project status
Version 1.0.0-draft.1 — Draft specification under active development. Design rationale lives in Architecture Decision Records. See the project build story for how the specification, schemas, and both implementations were built in three weeks.