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

yamlforms

v1.8.0

Published

Generate fillable PDF forms with AcroForm fields from YAML schemas

Readme

yamlforms

npm version License: MIT

Generate fillable PDF forms, interactive HTML forms, and styled DOCX documents from YAML schema definitions.

Features

  • Multi-format output -- PDF with AcroForm fields, interactive HTML, and styled DOCX from a single schema
  • Rich content types -- headings, paragraphs, tables, admonitions, horizontal rules, and spacers
  • Form field types -- text, textarea, checkbox, radio, dropdown, and signature
  • Tables with embedded form fields -- text, dropdown, and checkbox cells inside table rows
  • Calculated fields -- formulas that compute values from other fields (number, currency, percentage, text)
  • Conditional logic -- show, hide, enable, or disable fields based on trigger values
  • CSS-based styling -- custom stylesheets with var() support; WCAG 2.1 AA compliant defaults
  • Flow layout -- content stacks vertically with automatic page breaks
  • Auto-numbered labels -- optional sequential numbering for standalone fields
  • Watch mode -- auto-regenerate on file changes during development
  • GitHub Action -- generate documents and publish to GitHub Pages in CI/CD

Installation

npm install -g yamlforms

Requirements: Node.js 24+

Quick Start

# Initialize a new project
yamlforms init my-forms
cd my-forms

# Generate a PDF from the sample schema
yamlforms generate schemas/sample-form.yaml

# Generate multiple formats
yamlforms generate schemas/sample-form.yaml --format pdf,html,docx

# Watch for changes during development
yamlforms generate schemas/sample-form.yaml --watch

CLI Reference

generate <schema>

Generate documents from a form schema.

| Option | Description | Default | | ------------------------ | ------------------------------------------------------- | ----------------- | | -o, --output <dir> | Output directory | current directory | | -f, --format <formats> | Comma-separated formats: pdf, html, docx | pdf | | -c, --config <path> | Path to configuration file | auto-detected | | -w, --watch | Watch schema and config for changes, regenerate on save | -- | | -v, --verbose | Verbose output | -- |

validate <schema>

Validate a schema file against the JSON schema.

| Option | Description | | --------------- | ----------------------------- | | -v, --verbose | Show detailed validation info |

preview <schema>

Preview form fields from a schema without generating files (dry run).

| Option | Description | Default | | ----------------------- | -------------------------------------- | ------- | | -f, --format <format> | Output format: table, json, yaml | table |

init [directory]

Scaffold a new project. Creates:

  • schemas/ with a sample form schema
  • styles/ directory
  • dist/ output directory
  • yamlforms.config.yaml with default settings
  • .gitignore

Schema Reference

Basic Structure

$schema: 'form-schema/v1'

form:
  id: my-form
  title: My Form
  version: '1.0.0'
  author: Jane Doe
  description: A sample form
  pages: 1
  positioning: flow
  numbering: true
  stylesheet: './custom.css'

content:
  - type: heading
    level: 1
    text: 'Form Title'

  - type: field
    label: 'Full Name'
    fieldType: text
    fieldName: full_name
    required: true

fields: []

calculations: []

conditionalFields: []

validation:
  rules: []

Form Metadata

| Property | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ------------------------------------------------------------- | | id | string | yes | -- | Unique form identifier. Must match ^[a-zA-Z][a-zA-Z0-9_-]*$ | | title | string | yes | -- | Form title (non-empty) | | version | string | no | -- | Semver version (X.Y.Z) | | author | string | no | -- | Author name | | description | string | no | -- | Form description | | pages | integer | no | 1 | Number of pages (minimum 1) | | positioning | string | no | flow | Positioning mode. Only flow is supported | | numbering | boolean | no | false | Auto-number standalone field labels (1., 2., 3., ...) | | stylesheet | string | no | -- | Path to a custom CSS stylesheet |

Content Types

Content elements are defined in the content array. Each element has a type and optional page and position properties (rarely needed in flow mode).

heading

- type: heading
  level: 2 # 1-6
  text: 'Section Title'

paragraph

- type: paragraph
  text: 'Body text content.'
  maxWidth: 400 # Maximum width in points (optional)
  fontSize: 12 # Font size in points, 6-72 (optional)

field

Standalone form field rendered in the content flow.

- type: field
  label: 'Email Address'
  labelPosition: above # above (default) or left
  labelWidth: 120 # Width in points when labelPosition is left
  fieldType: text # text, dropdown, checkbox, textarea
  fieldName: email # Unique identifier, ^[a-zA-Z][a-zA-Z0-9_]*$
  width: 300 # Field width in points (default: content width)
  height: 60 # Field height in points (for textarea)
  placeholder: '[email protected]'
  default: ''
  required: true
  options: [] # For dropdown fields (see Field Options)

table

See the Tables section below.

admonition

- type: admonition
  variant: warning # warning, note, info, tip, danger
  title: 'Important'
  text: 'Please read carefully before proceeding.'

rule

Horizontal line separator.

- type: rule

spacer

- type: spacer
  height: 24 # Height in points (minimum 1)

Tables

Tables support three approaches for defining rows.

Column Definition

Each column requires label and width. Optional properties set defaults for all cells in the column.

| Property | Type | Required | Description | | ------------- | ------ | -------- | --------------------------------------------------------------- | | label | string | yes | Column header text | | width | number | yes | Column width in points (minimum 10) | | cellType | string | no | Default cell type: text, dropdown, checkbox, label | | fieldSuffix | string | no | Suffix for auto-generated field names (used with fieldPrefix) | | options | array | no | Options for dropdown cells in this column |

Approach 1: Manual Cells

Each row defines an array of cell objects.

- type: table
  columns:
    - { label: 'Name', width: 150 }
    - { label: 'Role', width: 120, cellType: dropdown, options: ['Dev', 'QA', 'PM'] }
    - { label: 'Active', width: 60 }
  rows:
    - cells:
        - { type: text, fieldName: name_1 }
        - { type: dropdown, fieldName: role_1, options: ['Dev', 'QA', 'PM'] }
        - { type: checkbox, fieldName: active_1 }

Cell types:

| Cell Type | Required Properties | Optional | | ---------- | --------------------- | -------------------- | | label | value (static text) | -- | | text | fieldName | default | | dropdown | fieldName | options, default | | checkbox | fieldName | default |

Approach 2: Compact Values

Each row provides a values array. The cell type is inferred from the column's cellType (or defaults to text). Strings become label values or field names depending on the column type.

- type: table
  columns:
    - { label: 'Item', width: 200, cellType: label }
    - { label: 'Qty', width: 80, cellType: text }
  rows:
    - values: ['Widget A', 'qty_1']
    - values: ['Widget B', 'qty_2']

Approach 3: Auto-generated Rows

Use rowCount and fieldPrefix to generate identical rows. Field names are built as {fieldPrefix}_row{N}_{fieldSuffix}.

- type: table
  fieldPrefix: timesheet
  rowCount: 5
  columns:
    - { label: 'Date', width: 100, cellType: text, fieldSuffix: date }
    - { label: 'Hours', width: 80, cellType: text, fieldSuffix: hours }
    - { label: 'Billable', width: 60, cellType: checkbox, fieldSuffix: billable }

Table Properties

| Property | Type | Default | Description | | -------------- | ------- | ---------- | ---------------------------------------------- | | label | string | -- | Optional description displayed above the table | | columns | array | (required) | Column definitions | | rows | array | -- | Explicit row definitions | | fieldPrefix | string | -- | Base name for auto-generated field names | | rowCount | integer | -- | Number of rows to auto-generate (1-50) | | rowHeight | number | 22 | Data row height in points (minimum 10) | | headerHeight | number | 24 | Header row height in points (minimum 10) | | showBorders | boolean | true | Show table borders |

Legacy Positioned Fields

The fields array supports absolute positioning for precise control. Each field requires explicit page and position coordinates.

| Property | Type | Required | Description | | ----------------- | --------------------- | -------- | ---------------------------------------------------------------- | | name | string | yes | Field identifier (^[a-zA-Z][a-zA-Z0-9_]*$) | | type | string | yes | text, checkbox, radio, dropdown, textarea, signature | | label | string | yes | Field label | | page | integer | yes | Page number (1-indexed) | | position | object | yes | { x, y, width?, height? } in points | | required | boolean | no | Whether the field is required | | placeholder | string | no | Placeholder text | | maxLength | integer | no | Maximum character length | | multiline | boolean | no | Enable multiline for text fields | | options | array | no | Options for radio and dropdown fields | | default | string/boolean/number | no | Default value | | validation | object | no | Validation rules (see below) | | fontSize | number | no | Font size in points (6-72, default: 12) | | fontColor | string | no | Hex color (#RRGGBB) | | backgroundColor | string | no | Hex color (#RRGGBB) | | borderColor | string | no | Hex color (#RRGGBB) | | labelPosition | string | no | left, right, above, none | | readOnly | boolean | no | Make the field read-only | | hidden | boolean | no | Hide the field |

Field Options

Options can be specified as objects or as simple strings:

# Object format
options:
  - value: 'us'
    label: 'United States'
  - value: 'ca'
    label: 'Canada'

# String format (value = label)
options:
  - 'United States'
  - 'Canada'

Field Validation

Applied on legacy positioned fields via the validation property:

| Property | Type | Description | | ----------- | ------- | -------------------------------------- | | pattern | string | Regex pattern | | message | string | Error message shown on pattern failure | | min | number | Minimum numeric value | | max | number | Maximum numeric value | | minLength | integer | Minimum character length | | maxLength | integer | Maximum character length |

fields:
  - name: email
    type: text
    label: 'Email'
    page: 1
    position: { x: 72, y: 700, width: 250 }
    validation:
      pattern: '^[\w.-]+@[\w.-]+\.\w+$'
      message: 'Enter a valid email address'

Calculated Fields

Define formulas that reference other field names. Calculations are evaluated in order, so later calculations can reference earlier ones.

calculations:
  - name: subtotal
    formula: 'quantity * price'
    format: currency
    decimals: 2

  - name: tax
    formula: 'subtotal * 0.1'
    format: currency
    decimals: 2

  - name: total
    formula: 'subtotal + tax'
    format: currency
    decimals: 2

| Property | Type | Required | Description | | ---------- | ------- | -------- | ------------------------------------------------- | | name | string | yes | Calculated field name (^[a-zA-Z][a-zA-Z0-9_]*$) | | formula | string | yes | Expression referencing other fields | | format | string | no | number, currency, percentage, text | | decimals | integer | no | Decimal places (0-10) |

Conditional Fields

Show, hide, enable, or disable fields based on a trigger field's value.

conditionalFields:
  - trigger:
      field: employment_type
      value: 'contractor'
      operator: equals # equals (default), not_equals, contains, greater, less
    show: ['hourly_rate', 'contract_end_date']
    hide: ['annual_salary']

  - trigger:
      field: has_dependents
      value: true
    show: ['dependent_count', 'dependent_names']

| Property | Type | Description | | ------------------ | --------------------- | ----------------------------------------------------- | | trigger.field | string | Field name to watch | | trigger.value | string/boolean/number | Value to compare | | trigger.operator | string | equals, not_equals, contains, greater, less | | show | string[] | Fields to show when condition is met | | hide | string[] | Fields to hide when condition is met | | enable | string[] | Fields to enable when condition is met | | disable | string[] | Fields to disable when condition is met |

Validation Rules

Top-level rules that validate cross-field relationships:

validation:
  rules:
    - if: 'end_date < start_date'
      then: 'error: End date must be after start date'

    - if: 'quantity > 100'
      then: 'warning: Large orders require manager approval'

The then string can be prefixed with error:, warning:, or info: to set severity.

Styling

yamlforms uses a CSS-based styling system parsed with css-tree. The default stylesheet follows WCAG 2.1 AA, ISO 9241-115, and PDF/UA standards.

Custom Stylesheet

Point to a custom CSS file from the schema:

form:
  id: my-form
  title: My Form
  stylesheet: './custom.css'

Supported Selectors

| Selector | Targets | | ---------------------------------------------------------------------------------------------------------- | --------------------------------- | | :root | CSS custom properties (variables) | | @page | Page size and margins | | h1 -- h6 | Heading levels | | p | Paragraphs | | hr | Horizontal rules | | .header | Page header | | .footer | Page footer | | .admonition | Base admonition styles | | .admonition-title | Admonition title text | | .admonition-content | Admonition body text | | .admonition-warning, .admonition-note, .admonition-info, .admonition-tip, .admonition-danger | Variant-specific styles | | .field | Base form field styles | | .field-text, .field-textarea, .field-checkbox, .field-radio, .field-dropdown, .field-signature | Field-type-specific styles | | .field-label | Field label text | | .table | Base table styles | | .table-header | Table header row | | .table-row | Table data rows | | .table-cell | Table cell styles | | .table-row-alternate | Alternate row background |

CSS Variables

Define variables in :root and reference them with var():

:root {
  --brand-color: #003366;
  --brand-font: HelveticaBold;
}

h1 {
  color: var(--brand-color);
  font-family: var(--brand-font);
}

Supported Units

| Unit | Description | Conversion | | ---- | ----------- | ----------------- | | pt | Points | 1pt (base unit) | | px | Pixels | 1px = 0.75pt | | in | Inches | 1in = 72pt | | cm | Centimeters | 1cm = 28.35pt | | mm | Millimeters | 1mm = 2.835pt | | em | Em | 1em = 12pt (base) |

Available Fonts

| Font Name | Variant | | ---------------------- | ------------ | | Helvetica | Regular | | HelveticaBold | Bold | | HelveticaOblique | Oblique | | HelveticaBoldOblique | Bold Oblique | | Courier | Regular | | CourierBold | Bold | | CourierOblique | Oblique | | CourierBoldOblique | Bold Oblique | | TimesRoman | Regular | | TimesRomanBold | Bold | | TimesRomanItalic | Italic | | TimesRomanBoldItalic | Bold Italic |

Configuration

yamlforms auto-detects configuration files by searching up the directory tree for:

  • yamlforms.config.yaml / yamlforms.config.yml / yamlforms.config.json
  • .yamlformsrc / .yamlformsrc.yaml / .yamlformsrc.yml / .yamlformsrc.json

Example Configuration

input:
  schemas: './schemas'
  styles: './styles'

output:
  directory: './dist'
  formats:
    - pdf
    - html
  filenameTemplate: '{name}'

pdf:
  pageSize: letter
  margins:
    top: 72
    bottom: 72
    left: 72
    right: 72
  fonts:
    default: Helvetica
    monospace: Courier
  embedFonts: false
  compress: true

html:
  embedStyles: true
  includeJs: true
  pageLayout:
    enabled: true
    pageSize: letter
    showPageNumbers: true

docx:
  pageSize: letter
  margins:
    top: 72
    bottom: 72
    left: 72
    right: 72
  fonts:
    default: Helvetica
    monospace: Courier
  formFieldStyle: box # underline, box, or shaded
  includeFormFieldHints: true

Filename Template Variables

| Variable | Description | | ----------- | ------------------------------------- | | {name} | Schema form ID | | {format} | Output format (pdf, html, docx) | | {version} | Form version from schema (or 1.0.0) | | {date} | Current date (YYYY-MM-DD) |

Page Sizes

| Size | Dimensions (points) | Dimensions (inches) | | --------- | ------------------- | ------------------- | | letter | 612 x 792 | 8.5 x 11 | | a4 | 595.28 x 841.89 | 8.27 x 11.69 | | legal | 612 x 1008 | 8.5 x 14 | | tabloid | 792 x 1224 | 11 x 17 |

GitHub Action

Basic Usage

- uses: robinmordasiewicz/yamlforms@v1

Uses default settings: generates PDFs from schemas/*.yaml into dist/.

Inputs

| Input | Description | Default | | ---------------- | ------------------------------------------------------------------------ | --------------------- | | command | Command to run: generate or validate | generate | | schema | Path to YAML schema file or glob pattern | schemas/*.yaml | | output | Output directory for generated files | dist | | format | Output formats: pdf, html, docx (comma-separated) | pdf | | fail-on-error | Fail the action if validation errors occur | true | | publish | Enable GitHub Pages publishing | true | | publish-method | pages-api (requires deploy-pages action) or branch (pushes directly) | pages-api | | pages-branch | Branch name for branch-based publishing | gh-pages | | generate-index | Generate index.html listing all documents | true | | index-title | Title for the generated index page | Generated Documents |

Outputs

| Output | Description | | --------------------- | -------------------------------------------------------------- | | files | JSON array of generated file paths | | pdf-count | Number of PDF files generated | | html-count | Number of HTML files generated | | docx-count | Number of DOCX files generated | | validation-errors | JSON array of validation errors (if any) | | pages-url | URL of deployed GitHub Pages site | | index-file | Path to generated index.html | | pages-artifact-path | Path for upload-pages-artifact (use with pages-api method) |

Workflow Examples

Generate and upload as artifacts:

- uses: robinmordasiewicz/yamlforms@v1
  id: yamlforms
  with:
    schema: 'schemas/*.yaml'
    format: 'pdf,html,docx'
    output: 'dist'

- uses: actions/upload-artifact@v4
  with:
    name: forms
    path: dist/

Deploy to GitHub Pages (pages-api method -- recommended):

permissions:
  pages: write
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - uses: actions/checkout@v4

      - uses: robinmordasiewicz/yamlforms@v1
        with:
          schema: 'schemas/*.yaml'
          format: 'pdf,html,docx'

      - uses: actions/upload-pages-artifact@v4
        with:
          path: dist

      - uses: actions/deploy-pages@v4
        id: deployment

Deploy to GitHub Pages (branch method):

permissions:
  contents: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: robinmordasiewicz/yamlforms@v1
        with:
          schema: 'schemas/*.yaml'
          format: 'pdf,html,docx'
          publish-method: branch
          pages-branch: gh-pages
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Validate schemas in pull requests:

- uses: robinmordasiewicz/yamlforms@v1
  with:
    command: validate
    schema: 'schemas/**/*.yaml'

Generate only (no publishing):

- uses: robinmordasiewicz/yamlforms@v1
  with:
    schema: 'schemas/*.yaml'
    publish: 'false'

Development

git clone https://github.com/robinmordasiewicz/yamlforms.git
cd yamlforms
npm install

npm run build        # Compile TypeScript
npm test             # Run tests
npm run typecheck    # Type checking
npm run lint         # Linting

License

MIT