Free Demo
Upgrade to PRO for production workflows

Rowio: Vanilla JavaScript Repeatable Form Rows

Build repeatable form rows, dynamic form fields, and repeatable inputs from a single <template> with zero dependencies, hard reindexing, copy-down, JSON prefill, and clean DOM events.

Free
Lightweight repeatable rows, clean events, quick setup, zero dependencies.
PRO
Built for serious backoffice forms with richer workflows, more power, and less custom glue code.
Vanilla JS
Framework-agnostic, dependency-free, production-ready repeatable form rows for modern web apps.
Backend Friendly
Predictable field naming, explicit config, and harvestable payloads for complex dynamic form fields.
Integration Hooks
Lifecycle events for plugins, validation, editors, and custom logic in repeatable form sections.
What It Is

Rowio is a lightweight vanilla JavaScript library for building repeatable form rows, repeatable inputs, and dynamic form fields.

Rowio is suitable for admin panels, SaaS back offices, invoice editors, settings pages, and complex forms where users need to add, remove, and manage repeatable form sections dynamically.

Initialize Rowio on any element with the .rowio CSS class and configure your JavaScript form repeater using data-* attributes on the wrapper element.

Best Use Cases For Repeatable Form Rows

Use Cases

Rowio is designed for forms where one field group must be repeated multiple times without building custom JavaScript every time.

Invoices And Quotes
Line items, quantities, VAT rates, and pricing tables with repeatable invoice rows.
Admin CRUD Forms
Product variants, options, metadata groups, and configurable repeatable field sections.
Backoffice SaaS
Internal tools where users manage many related records from a single form view.
Settings And Config
Rules, conditions, routing rows, notifications, and custom repeatable input groups.

FAQ About Rowio And Repeatable Form Rows

FAQ

What is Rowio?

Rowio is a vanilla JavaScript library for repeatable form rows, dynamic form fields, repeatable inputs, JSON prefill, copy-down behavior, and clean DOM events.

What types of forms is Rowio good for?

Rowio is a strong fit for invoices, quotes, product variant forms, admin CRUD screens, SaaS backoffice tools, and repeatable settings sections.

Does Rowio need React, Vue, or jQuery?

No. Rowio is framework-agnostic and works as a plain vanilla JavaScript form repeater.

When should I choose Rowio PRO?

Choose Rowio PRO when your team wants a more complete workflow layer for production forms and less custom engineering around repeatable form behavior.

Free vs PRO: Which Rowio Version Should You Use?

Free vs PRO

Use Rowio Free when you want a lightweight repeatable form rows library with explicit setup and zero dependencies. Upgrade to Rowio PRO when your forms become a bigger product surface and you want a faster path to richer workflows.

Rowio Free
Best for evaluation, lightweight forms, simple admin screens, and teams that want a transparent vanilla JavaScript base.
Rowio PRO
Best for production backoffice products, internal tools, and teams that want to reduce custom form engineering work.

Why Teams Upgrade To PRO

Rowio PRO

Rowio Free gives you the core repeatable-row engine. Rowio PRO is for teams building real admin products where speed, polish, and less custom integration work matter.

Ship Faster
Spend less time wiring edge cases and more time finishing production-ready forms.
Richer Workflows
Move beyond basic row repetition into workflows that fit serious SaaS and backoffice use.
Less Custom Glue
Reduce hand-rolled UI logic and avoid re-solving the same repeatable-form problems.
Production Focus
A better fit when forms stop being demos and start becoming important product surfaces.
See Rowio PRO
Best for teams building internal tools, admin panels, invoicing flows, and complex CRUD interfaces.

Why Choose Rowio Instead Of Custom JavaScript

Why Rowio

Many teams start with hand-written JavaScript for dynamic form rows, then spend time fixing indexing, deleting rows, copying values, and serializing data consistently.

Rowio gives you a reusable, explicit, backend-friendly repeatable form row pattern so you can stop rebuilding the same form repeater logic across projects.

Less Glue Code
Use declarative HTML attributes instead of shipping custom scripts for every repeatable form.
Predictable Naming
Keep field indexing and payload harvesting consistent across add, remove, and reindex operations.

How Rowio Initializes Repeatable Form Rows

Rowio.init();

Rowio is initialized manually by calling Rowio.init(). This gives you full control over when and where repeatable rows become active.

For Rowio to initialize successfully, the following HTML structure is required.

  • A wrapper element with the class .rowio
  • A <template> element with class .rowio-template
  • Inside the template, exactly one element with class .rowio-row
  • Inputs inside the template must use unindexed field names (Rowio will prefix and index them automatically)

Minimal example:

<div class="rowio" data-rowio-prefix="items">
  <template class="rowio-template">
    <div class="rowio-row">
      <input name="price">
    </div>
  </template>
</div>

When initialized, Rowio will:

  • Clone the template row
  • Insert rows into a .rowio-rows container
  • Automatically rewrite field names to prefix__field__index
  • Emit lifecycle events such as rowio:ready

Initialization is usually done once after the DOM is ready:
document.addEventListener('DOMContentLoaded', () => Rowio.init());

JavaScript Form Repeater Configuration Via Data Attributes

HTML-driven

Rowio is configured entirely through data-* attributes. No JavaScript configuration objects are required. This keeps Rowio declarative, predictable, and framework-agnostic.

Wrapper-level attributes (.rowio)
  • data-rowio-prefix
    Defines the row group prefix used in input names. Example output name: items__price__0
  • data-rowio-shown
    Number of rows rendered initially when no prefill data is present. Minimum is always 1.
  • data-rowio-max
    Maximum number of rows allowed. Use 0 or omit for unlimited rows.
  • data-rowio-copy-down
    Comma-separated list of field names that support copy-down behavior. Copy-down buttons appear on the first row only. Example: data-rowio-copy-down="price,tax"
  • data-rowio-copy-down-class
    Optional CSS classes applied to copy-down buttons. Useful for custom styling.
Template-level attributes (<template>)
  • data-rowio-key
    Overrides the instance key used for backend harvesting. When present, Rowio injects a hidden input mapping the prefix to this key. This allows backend grouping without renaming inputs.
Field-level attributes (inputs / editors)
  • data-rowio-default
    Default value applied when a row is created and no prefill data exists.
  • data-rowio-html
    Used on contenteditable="true" elements. When set to 1 or true, Rowio writes and reads raw HTML instead of text.

All configuration is intentionally explicit. Rowio never infers behavior from markup structure alone.

Events And Integration Hooks For Dynamic Form Fields

CustomEvent

Rowio emits DOM events on the .rowio wrapper element. Each event is a CustomEvent and carries its payload in event.detail.

List of emitted events
  • rowio:ready
    Fired after the instance is initialized and initial rows are rendered. Payload contains instance and current rows snapshot.
  • rowio:row-add
    Fired after a new row is added and rows reindexed. Immediately followed by rowio:change.
  • rowio:row-remove
    Fired after a row is removed and remaining rows are reindexed. Immediately followed by rowio:change. Note: payload typically has row: null because the row is gone.
  • rowio:copy-down
    Fired after copy-down writes values into subsequent rows. Immediately followed by rowio:change.
  • rowio:change
    Fired after a user-visible mutation happens (add / remove / copy-down). Use this as the recalculate / revalidate / re-init hook.
  • rowio:max-rows-reached
    Fired when a row add is attempted but rejected because data-rowio-max is reached. No mutation happens. No row is created.
Common payload shape

Most events expose these keys inside event.detail:

  • instance - the Rowio instance
  • row - the affected row element (or null)
  • index - row index (0-based) when applicable
  • fields - map of field name to element or array of elements (when applicable)
  • editors - list of WYSIWYG candidates in the row (when applicable)
Example usage

Listen on a wrapper and react to events:


    // wrapper = the .rowio element
    const wrapper = document.querySelector('.rowio');

    // Re-init plugins / validation after any structural change
    wrapper.addEventListener('rowio:change', (e) => {

        const { instance, row, index, fields, editors } = e.detail;

        // Example: init Select2 / Choices on the affected row only
        if (row) {
            // init_select2(row.querySelectorAll('.select2-select'));
            // init_choices(row.querySelectorAll('.choices-select'));
        }

        // Example: re-init WYSIWYG (Rowio does not do this automatically)
        // editors.forEach(el => tinymce_init(el));
    });

    // Show alert when maximum number of rows is reached
    wrapper.addEventListener('rowio:max-rows-reached', (e) => {
        const { max, rows_count } = e.detail;
        // alert(`Max rows reached (${rows_count}/${max})`);
                    });
                    

1) Basic repeatable rows

add / remove / hard reindex

Template fields use bare names and optionally ids (e.g. opt_value, opt_label). Rowio rewrites them to prefix___field__0, prefix___field__1 etc.

In this case the prefix is enum, so the full field names become enum__opt_value__0, enum__opt_label__0, enum__opt_value__1, enum__opt_label__1 etc.

Try: Click the + and × buttons to add/remove rows.

2) Copy-down controls (field names)

data-rowio-copy-down
Copy-down buttons appear next to configured fields in the first row. Clicking copies value down to all subsequent rows.
Try: Set e-mail/VAT in first row by clicking the button. It should fill all next rows.

3) JSON prefill inside template + max rows

script.rowio-data + data-rowio-max
Rowio reads JSON from <script type="application/json" class="rowio-data"> inside the template and renders those rows. Max rows blocks adding beyond the limit.
max=6
Try: Copy-down VAT. Try adding rows until max is reached.

4) contenteditable="true" field (HTML mode)

contenteditable + data-rowio-html
This demonstrates how Rowio treats contenteditable="true" elements as value carriers.
With data-rowio-html="true", Rowio uses innerHTML (not textContent).
Security note:
Rowio does not sanitize HTML. If this content comes from users, sanitize on the server.
Live Telemetry

Event log (integration hooks)

Rowio emits events on the wrapper element.

This log shows the sequence you can hook into (e.g. init plugins, re-init editors, custom validation, etc.).

Go PRO

Building serious admin forms?

Use Free to evaluate the core engine. Upgrade to PRO when you need a more complete workflow layer and less custom implementation overhead.

Explore Rowio PRO
Rowio Free demo page • Bootstrap 5 • No dependencies • PRO available for production teams