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

@cas0570/flowforms

v0.1.1

Published

A configurable, embeddable smart form engine for multi-step workflows

Downloads

194

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/flowforms

Or 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: true

Supports 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: 2

The 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 Name

Custom 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 up

Events

| 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

License

MIT