Read-only questionnaire form renderer for eSheet. Renders forms in fill-out mode with conditional visibility logic.
- ✅ Renders all 19 eSheet field types (reuses
@esheet/fieldscomponents) - ✅ Conditional visibility enforcement (fields/sections hide based on logic rules)
- ✅ Section nesting with recursive rendering
- ✅ Initial response pre-fill support
- ✅ YAML/JSON schema parsing with Zod validation
- ✅ Ref API for collecting responses
- ✅ TypeScript-first with full type safety
npm install @esheet/renderer @esheet/fields @esheet/coreStandalone and Blaze integrations now ship as separate packages:
npm install @esheet/renderer-standalone
npm install @esheet/renderer-blazeMigration for old subpath imports:
@esheet/renderer/standalone->@esheet/renderer-standalone@esheet/renderer/blaze->@esheet/renderer-blaze
import { EsheetRenderer } from '@esheet/renderer';
import type { FormDefinition } from '@esheet/core';
const myForm: FormDefinition = {
schemaType: 'mieforms-v1.0',
title: 'Patient Intake',
fields: [
{
id: 'name',
fieldType: 'text',
question: 'Full Name',
required: true,
},
{
id: 'age',
fieldType: 'text',
question: 'Age',
},
],
};
function App() {
return (
<div className="app-container">
<EsheetRenderer formData={myForm} />
</div>
);
}import { useRef } from 'react';
import { EsheetRenderer, type EsheetRendererHandle } from '@esheet/renderer';
function App() {
const rendererRef = useRef<EsheetRendererHandle>(null);
const handleSubmit = () => {
const responses = rendererRef.current?.getResponse();
console.log('Form responses:', responses);
// { name: '...', age: '...' }
};
return (
<>
<EsheetRenderer formData={myForm} ref={rendererRef} />
<button onClick={handleSubmit}>Submit</button>
</>
);
}<EsheetRenderer
formData={myForm}
initialResponses={{
name: 'John Doe',
age: '42',
}}
/>const yamlSchema = `
schemaType: mieforms-v1.0
title: Simple Form
fields:
- id: q1
fieldType: text
question: Your name?
`;
<EsheetRenderer formData={yamlSchema} />;Props:
formData: FormDefinition | string- Form schema (object, JSON string, or YAML string)initialResponses?: FormResponse- Pre-fill form with initial dataclassName?: string- Additional CSS classes for root containerref?: Ref<EsheetRendererHandle>- Access ref API for collecting responses
Ref API:
interface EsheetRendererHandle {
getResponse: () => FormResponse;
getFormStore: () => FormStore;
getUIStore: () => UIStore;
}EsheetRenderer is a thin wrapper that:
- Creates form and UI stores (vanilla Zustand)
- Parses and validates input (YAML/JSON → Zod schema check)
- Loads definition into store
- Sets preview mode (read-only, no editing UI)
- Iterates over visible fields via
RendererBody - Renders each field via
FieldNode(uses@esheet/fieldscomponents)
Conditional Logic:
- Reuses
form.isVisible(),form.isEnabled(),form.isRequired()from core - Sections auto-hide when all children are invisible
- Field visibility updates reactively when responses change
Section Nesting:
FieldNoderecursively renders section children- Each depth level adds left border and padding
- Respects visibility rules at every level
const conditionalForm: FormDefinition = {
schemaType: 'mieforms-v1.0',
title: 'Conditional Form',
fields: [
{
id: 'hasAllergies',
fieldType: 'boolean',
question: 'Do you have any allergies?',
},
{
id: 'allergyList',
fieldType: 'longtext',
question: 'Please list your allergies',
visible: {
conditions: [
{
conditionType: 'comparison',
fieldId: 'hasAllergies',
operator: '==',
value: true,
},
],
logicalOperator: 'AND',
},
},
],
};
// "allergyList" only shows when "hasAllergies" is checked
<EsheetRenderer formData={conditionalForm} />;The renderer uses Tailwind CSS v4 with ms: prefix. CSS is compiled via @tailwindcss/cli and embedded into the JS bundle at build time — consumers never need to import a stylesheet. A scoped reset on .esheet-renderer-root prevents style leakage in either direction. Dark mode is supported via .dark class on the root.
MIT
Run nx build @esheet/renderer to build the library.
Run nx test @esheet/renderer to execute the unit tests via Vitest.