@cas0570/flowforms
v0.1.1
Published
A configurable, embeddable smart form engine for multi-step workflows
Downloads
194
Maintainers
Readme
Features
| Feature | Description |
|---------|-------------|
| Multi-step forms | Progress bar, step navigation, conditional step visibility |
| YAML or JSON config | Define forms declaratively — no code required |
| Conditional logic | Show/hide fields and steps based on user input |
| Calculated fields | Auto-compute values with a safe expression engine |
| API lookups | Fetch and map external data on blur or button click |
| Progress saving | Auto-save/restore with pluggable storage adapters |
| Reusable components | Define field templates, reuse them across steps |
| 12 field types | text, email, number, date, textarea, select, radio, checkbox, file, signature (draw + type), address |
| Internationalization | Fully customizable locale strings — ship any language |
| Dark mode | Manual (.ff-dark class) or auto via JS media query check |
| High contrast | Respects prefers-contrast: more for accessibility |
| Custom CSS | Inject arbitrary CSS via customCSS option |
| Accessible | ARIA attributes, keyboard navigation, focus management, live announcements |
| Responsive | Mobile-first, adapts at 600 px breakpoint |
| Lightweight | ~32 KB gzipped, zero framework dependencies |
Installation
npm install @cas0570/flowformsOr load via CDN (UMD):
<script src="https://unpkg.com/@cas0570/flowforms"></script>
<script>
FlowForms.render('#form', { /* config */ });
</script>Quick Start
From YAML
import { FlowForms } from '@cas0570/flowforms';
const yaml = `
form_id: contact
version: 1
title: Contact Us
steps:
- id: info
title: Your Information
fields:
- id: name
type: text
label: Full Name
required: true
- id: email
type: email
label: Email Address
required: true
validation:
- type: email
message: Please enter a valid email.
- id: message
type: textarea
label: Message
`;
const form = FlowForms.render('#form-container', yaml, {
onSubmit: (values) => {
console.log('Submitted:', values);
},
});From JavaScript Object
const form = FlowForms.render('#form-container', {
form_id: 'feedback',
version: 1,
steps: [{
id: 'step1',
fields: [
{ id: 'rating', type: 'select', label: 'Rating', options: ['1','2','3','4','5'] },
{ id: 'comment', type: 'textarea', label: 'Comments' },
],
}],
}, {
onSubmit: (values) => fetch('/api/feedback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
}),
});Options
FlowForms.render(target, config, {
onSubmit: (values) => {}, // Called on form submission
onChange: (values) => {}, // Called on every field change
onStepChange: (index, id) => {},// Called on step navigation
initialValues: { name: 'Jane' },// Pre-fill values
restoreProgress: true, // Restore saved progress on mount
customCSS: '.ff-title { color: red; }', // Inject custom CSS
locale: { /* partial FlowFormsLocale */ }, // Override UI strings
fetchAdapter: customFetch, // Custom fetch for API lookups
});Theming
Via config
theme:
primary_color: "#2563EB"
secondary_color: "#64748B"
error_color: "#EF4444"
success_color: "#22C55E"
font: "Inter, sans-serif"
border_radius: "12px"
spacing: "20px"Via CSS custom properties
.ff-root {
--ff-color-primary: #2563EB;
--ff-color-secondary: #64748B;
--ff-color-error: #EF4444;
--ff-color-success: #22C55E;
--ff-font-family: 'Inter', sans-serif;
--ff-border-radius: 12px;
--ff-spacing: 20px;
}Custom CSS injection
FlowForms.render('#app', config, {
customCSS: `
.ff-btn--primary {
background: linear-gradient(135deg, #667eea, #764ba2);
}
.ff-title {
text-align: center;
}
`,
});Dark mode
Dark mode activates when the .ff-dark class is present on any ancestor:
// Toggle manually
document.documentElement.classList.toggle('ff-dark');
// Or auto-detect OS preference
if (matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('ff-dark');
}Internationalization (i18n)
All UI strings (button labels, validation messages, announcements) are customizable via the locale option.
Example: Dutch locale
import { FlowForms } from '@cas0570/flowforms';
import type { FlowFormsLocale } from '@cas0570/flowforms';
const NL: Partial<FlowFormsLocale> = {
required: (label) => `${label} is verplicht.`,
minLength: (label, min) => `${label} moet minimaal ${min} tekens bevatten.`,
maxLength: (label, max) => `${label} mag maximaal ${max} tekens bevatten.`,
min: (label, min) => `${label} moet minimaal ${min} zijn.`,
max: (label, max) => `${label} mag maximaal ${max} zijn.`,
pattern: (label) => `${label} voldoet niet aan het vereiste patroon.`,
email: (label) => `${label} moet een geldig e-mailadres zijn.`,
next: 'Volgende',
previous: 'Vorige',
submit: 'Verzenden',
clear: 'Wissen',
};
FlowForms.render('#form', config, { locale: NL });You only need to override the strings you want to change — the rest falls back to English.
Available locale keys
| Key | Type | Default |
|-----|------|---------|
| required | (label) => string | "X is required." |
| minLength | (label, min) => string | "X must be at least N characters." |
| maxLength | (label, max) => string | "X must be at most N characters." |
| min | (label, min) => string | "X must be at least N." |
| max | (label, max) => string | "X must be at most N." |
| pattern | (label) => string | "X does not match the required pattern." |
| email | (label) => string | "X must be a valid email address." |
| next | string | "Next" |
| previous | string | "Previous" |
| submit | string | "Submit" |
| clear | string | "Clear" |
| lookup | string | "Lookup" |
| loading | string | "Loading…" |
| signatureLabel | string | "draw with mouse or touch" |
| signatureTypeLabel | string | "Type your signature" |
| signatureTypePlaceholder | string | "Type your full name" |
| signatureSwitchToDraw | string | "Switch to draw" |
| signatureSwitchToType | string | "Type instead" |
| stepAnnouncement | (current, total, title) => string | "Step 1 of 3: Title" |
| submitting | string | "Submitting form…" |
| submitted | string | "Form submitted successfully." |
| submissionError | (message) => string | "Submission error: …" |
| validationErrors | (count) => string | "N validation error(s) found." |
Conditional Logic
fields:
- id: has_pet
type: checkbox
label: Do you have a pet?
- id: pet_name
type: text
label: Pet's name
visible_when:
field: has_pet
operator: equals
value: trueSupports equals, not_equals, contains, not_contains, greater_than, less_than, is_empty, is_not_empty. Combine conditions with all / any groups.
Calculated Fields
fields:
- id: quantity
type: number
label: Quantity
- id: price
type: number
label: Unit Price
- id: total
type: number
label: Total
calculated:
expression: "quantity * price"
format: currency
currency: USD
precision: 2The expression engine supports +, -, *, /, %, min(), max(), round(), ceil(), floor(), abs(), and conditional if(cond, then, else).
API Lookups
fields:
- id: zip
type: text
label: ZIP Code
lookup:
url: "https://api.example.com/zip/{zip}"
trigger: blur # or "manual"
buttonLabel: Find # shown for manual trigger
mapping:
city: "result.city"
state: "result.state"Supply a custom fetchAdapter in options to use your own HTTP client or mock responses.
Reusable Components
components:
required_text:
type: text
required: true
validation:
- type: minLength
value: 2
steps:
- id: info
fields:
- id: first_name
use_component: required_text
label: First Name
- id: last_name
use_component: required_text
label: Last NameCustom Field Types
import { FlowForms } from '@cas0570/flowforms';
FlowForms.registerField('star-rating', (ctx) => {
const wrapper = document.createElement('div');
for (let i = 1; i <= 5; i++) {
const star = document.createElement('button');
star.type = 'button';
star.textContent = i <= (ctx.value || 0) ? '★' : '☆';
star.addEventListener('click', () => ctx.onChange(i));
wrapper.appendChild(star);
}
return wrapper;
});Programmatic API
const form = FlowForms.render('#app', config, options);
form.getValues(); // Current form values
form.setValues({ name: 'Jane' }); // Set values programmatically
form.goToStep(2); // Navigate to step index
form.saveProgress(); // Persist form state
form.clearProgress(); // Clear saved state
form.reset(); // Reset to initial values
form.on('field:change', (e) => {}); // Subscribe to events
form.destroy(); // Tear down and clean upEvents
| Event | Payload |
|-------|---------|
| field:change | { fieldId, value } |
| step:change | { fromIndex, toIndex, stepId } |
| validation:result | { fieldId, errors } |
| form:submit | { values } |
| form:reset | — |
| lookup:start | { fieldId } |
| lookup:success | { fieldId, data } |
| lookup:error | { fieldId, error } |
Progress Saving
FlowForms.render('#app', config, {
restoreProgress: true, // Restore on mount
});
// Or manually:
form.saveProgress();
form.clearProgress();By default uses localStorage. Provide a custom ProgressStorageAdapter for other backends.
Browser Support
Works in all modern browsers (Chrome, Firefox, Safari, Edge). Uses vanilla DOM APIs — no framework dependency.
Development
git clone https://github.com/casdoorn/flowforms.git
cd flowforms
npm install
npm run dev # Start Vite dev server
npm test # Run tests
npm run build # Production build
npm run lint # ESLint check
npm run typecheck # TypeScript check