Download the PHP package roro/roroform without Composer
On this page you can find all versions of the php package roro/roroform. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download roro/roroform
More information about roro/roroform
Files in roro/roroform
Package roroform
Short Description Laravel form components with repeatable groups that actually work — searchable selects, a chainable type-aware JS API, full old()/validation/CSRF integration, and a zero-CSS-framework theme. Zero JS dependencies.
License Apache-2.0
Homepage https://github.com/RodriguePoisson/RoroForm
Informations about the package roroform
RoroForm
Build full Laravel forms out of Blade components — and drive every field from JavaScript like it's 2026.
25+ input components · searchable selects · repeatable groups that actually work · a chainable, type-aware JS API · fully responsive & accessible · Tailwind, Bootstrap & framework-free themes — wired into Laravel's old(), validation errors and CSRF out of the box.
▶ Live playground · Quick start · Docs
Why RoroForm?
Most Laravel form helpers stop at rendering an <input>. RoroForm goes the whole way — from the Blade tag to the runtime behaviour in the browser.
- 🧩 One tag per field, zero boilerplate.
<x-roro-text>,<x-roro-select>,<x-roro-file>… 25+ components that render label, input, validation border, error message and required marker — consistently, in your theme. - 🔁 Repeatable groups that just work. Nest anything (including searchable selects, multi-selects and file inputs), add/remove/reorder rows, and submit a clean
contacts[0][name]array. Old input is restored after a failed validation with zero extra wiring. This is the feature other packages don't ship. - 🎛️ A real JavaScript API. A chainable, type-aware facade:
roro('email').value('[email protected]').required().focus(). The same.value()call reads/writes a text input, a select, a multi-select array, a checkbox boolean, a radio group or a list of file names. Most form packages ship no runtime API at all. - 🪶 Zero dependencies. The runtime is vanilla JavaScript — no jQuery, no framework, nothing to load on the page. Drop it into any stack (Livewire, Alpine, Inertia, Vue, React or plain Blade) without conflicts.
- 🔎 Smart selects, server-rendered. Searchable single & multi-selects with tags, option groups, and dynamic options — added client-side instantly or fetched from your JSON endpoint. Options render server-side, so there's no flash and no mandatory round-trip.
- 🪄 Laravel-native by default. Auto-repopulation from
old(), per-field error messages pulled straight fromsession('errors'), CSRF, AJAX submit with server-side validation errors mapped back onto the right fields. - 🎨 Three themes — including no framework at all. Ships Tailwind, Bootstrap, and a framework-free
rawtheme that brings its own stylesheet, so you get a clean modern look with zero CSS framework on the page. Switch with one config line; publish the views or the CSS to own them. - ♿ Accessible out of the box. Every theme ships real labels,
aria-describedbyerror wiring,aria-invalid/aria-required, fieldset/legend radio groups, and a full ARIA combobox for the custom selects — keyboard-navigable (↑↓, Enter, Escape, Home/End, search box) and screen-reader friendly. Most packages' custom selects are mouse-only. - 📱 Responsive & touch-friendly — the same UI on every device. Components flow from desktop to mobile out of the box: full-width controls, dropdowns and search bars that fit small screens, tags and radio groups that wrap, and comfortable tap targets. There's one custom UI everywhere — no device-specific
<select>fallback to second-guess, so what you test is what every visitor gets. - 📦 No build step for you. The vanilla-JS runtime is injected inline the first time a form renders. No npm, no Vite config, no bundler in your app.
RoroForm vs. the alternatives
If you've shopped for Laravel form tooling, you've met the usual options. Here's where RoroForm sits:
| Feature | RoroForm | Collective / Spatie helpers | Filament / Livewire |
|---|---|---|---|
| Nested repeatable groups (selects & files inside) | ✅ just works | ❌ | ⚠️ heavy stack |
| Runtime JS API to drive fields | ✅ chainable, type-aware | ❌ | ⚠️ partial |
| JS dependencies | none | varies | full framework |
| Works in plain Blade / any stack | ✅ | ✅ | ❌ tied to their runtime |
| Zero-CSS-framework option | ✅ raw theme |
❌ | ❌ |
| Searchable, accessible custom selects | ✅ ARIA combobox | ❌ | ✅ |
Laravel Collective's
Form/Htmlpackage is archived; Filament and Livewire are excellent, but they pull in a full reactive runtime. RoroForm aims at the middle: server-rendered Blade with a real JS layer, no framework lock-in.
Quick start
Requirements: PHP
^8.0· Laravel9 → 13. CSS is optional — therawtheme (framework-free) ships its own stylesheet, so you only need Tailwind or Bootstrap if you pick that theme. No JavaScript dependencies — the runtime is vanilla JS.
1. Install
2. Publish the config (optional but recommended)
This creates config/roroform.php:
3. Drop a form in any Blade view — no scripts to add:
That's it. Label, validation, error display, CSRF and the AJAX submit are all handled — and the assets are injected automatically. No <script src>, no @vite, nothing else to add.
A real form in 30 seconds
A taste of the breadth — every field below is a single tag:
📚 Documentation
Everything below is collapsed to keep this page scannable. Open the part you need.
🧱 The component catalog — every input type
Every component renders a full field: wrapper, optional label, the control, a validation border and an error slot — all themed. Names map onto Laravel's request payload exactly as you'd expect, including nested array names like `user[26][name]`. | Component | Renders | Notable props | |-----------|---------|---------------| | `
🧾 Every attribute, every component — the full reference
**Conventions.** Write string attributes plainly (`label="Email"`); add a **`:` prefix** to bind booleans, numbers, arrays and PHP expressions (`:required="true"`, `:options="[...]"`). camelCase props are written in **kebab-case** in Blade — `wrapperClass` → `wrapper-class`, `hasTopMargins` → `:has-top-margins`, `requirementsText` → `requirements-text`. Any attribute *not* listed below lands on the underlying element as a plain HTML attribute (see *Arbitrary HTML attributes pass-through*). #### Shared — every field input (from `InputMain`) Applies to **all** field components below (text, number, date, select, file, checkbox, radio, repeatable…) unless noted. | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | `id` | `string` | auto (`uniqid()`) | Element id; auto-generated when omitted. | | `name` | `string` | `''` | Field name — maps onto the request payload (supports nested `user[26][name]`). | | `label` | `string` | `null` | Label text rendered above/beside the control. | | `value` | `string` | `''` | Initial value (overridden by `old()` after a failed validation). | | `placeholder` | `string` | `''` | Placeholder text. | | `:required` | `bool` | `false` | Adds `required` + `aria-required`. | | `:disabled` | `bool` | `false` | Disables the control. | | `:readonly` | `bool` | `false` | Marks the control read-only. | | `:hidden` | `bool` | `false` | Renders the field's wrapper hidden (`display:none`). | | `class` | `string` | `''` | Extra classes on the **control** (merged with the `roro-*` classes). | | `wrapper-class` | `string` | `''` | Extra classes on the field **wrapper**. | | `label-class` | `string` | `''` | Extra classes on the **label**. | | `tooltip` | `string` | `null` | Tooltip text shown next to the label. | | `:enable-error` | `bool` | `true` | Render the inline error slot for this field. | | `:has-top-margins` | `bool` | `true` | Apply the default top margin to the field. | | `:populate` | `array` | `[]` | Candidate values for repopulation (first truthy wins; `old()` takes precedence). | | `:disable-js-validation` | `bool` | config | Drop the HTML validation attributes for this field. Defaults to the inverse of `defaultJsValidation`. | #### Text-like — `
🔎 Smart selects — searchable, grouped, dynamic
Options are rendered **server-side**, then read and enhanced by JS. No flash of an empty dropdown, no mandatory network round-trip — and the basic value is present even before JS runs. **Add options after load** — instantly, or from your own endpoint: Selects emit a real **`roro:change`** event — fired only on actual user/programmatic changes, never on initial population: > The legacy globals (`roroAddOption`, `roroAddOptionsAjax`, `roroDisableSelect`, …) still work unchanged — the `roro()` facade is purely additive on top of them.
⭐ Repeatable groups — the headline feature
`
🎛️ The roro() facade — one chainable, type-aware API
A small, **dependency-free** facade so you can drive any field without caring about its underlying markup. Everything is **chainable** and **type-aware** — the same call works on a text input, a select, a checkbox or a file field. `value()` is universal: text for inputs, the selected option for a `select`, an **array** for a multi-select, the checked state for a checkbox, the checked value for a radio group, file names for a file input. | Group | Methods | |-------|---------| | Value | `value(v?)` · `val(v?)` · `clear()` · `reset()` | | State | `disable(b=true)` · `enable()` · `readonly(b=true)` · `editable()` · `required(b=true)` · `optional()` · `isDisabled()` · `isReadonly()` · `isRequired()` | | Display | `show()` · `hide()` · `toggle(b?)` · `isVisible()` · `label(t?)` · `placeholder(t?)` | | Error | `error(msg)` · `clearError()` | | Events | `on(ev, fn)` · `off(ev, fn)` · `trigger(ev)` · `change(fn)` · `input(fn)` · `click(fn)` · `focus()` · `blur()` | | Misc | `type()` · `name()` · `exists()` · `$el()` · `$control()` · `$wrapper()` · `attr()` · `prop()` · `addClass()` / `removeClass()` / `toggleClass()` | **Select-only methods** (no-op elsewhere): `addOption` · `addOptions` · `addOptionsAjax` · `setOptions` · `removeOption` · `clearOptions` · `options` · `open` · `close`. #### Repeatables from JS Need one **specific row**? Prefer a **stable key** over a position — add `key-field="id"` to the component (with an `id` field, often hidden, per row):
📨 The roro.form() facade — drive the whole form
`fill()` matches keys against field **names** first (so it maps straight onto your server payload / `old()` data) and falls back to element **ids**. It even understands repeatable groups and custom selects, not just native inputs.
📋 Complete helper reference
Every flat `roro*(id, …)` global is a one-liner shortcut for the matching `roro(id).method(…)` call — use whichever reads better. `id` is the component's `id` (or, for forms, the form id). Below is the **full** list. #### Entry points & introspection | Helper | What it returns / does | Example | |--------|------------------------|---------| | `roro(target)` | A type-aware handle (field or form); `target` is an id or a DOM element | `roro('email').focus()` | | `roro.field(id)` | A field handle (explicit) | `roro.field('email')` | | `roro.form(id)` | A form handle | `roro.form('signup')` | | `roro.select(id)` | The underlying `RoroSelect`/`RoroMultiSelect` instance | `roro.select('country')` | | `roro.repeatable(id)` | The underlying `RoroRepeatable` instance | `roro.repeatable('contacts')` | | `roro.exists(target)` | `true` if the field/form is in the DOM | `if (roro.exists('email')) …` | | `roro.all(root?)` | Handles for every field under `root` (default: document) | `roro.all().forEach(h => h.clear())` | | `roro.ready(fn)` | Run `fn` on DOM ready | `roro.ready(() => …)` | | `roro.version` | The runtime version string | `roro.version // '2.0.0'` | | `roroGetSelect(id)` | Same as `roro.select(id)` | `roroGetSelect('country')` | | `roroGetRepeatable(id)` | Same as `roro.repeatable(id)` | `roroGetRepeatable('contacts')` | #### Field value & state | Helper | Does | Example | |--------|------|---------| | `roroValue(id, v?)` | Get (omit `v`) or set the value — type-aware | `roroValue('email', '[email protected]')` · `roroValue('email')` | | `roroClear(id)` | Clear / reset the field | `roroClear('email')` | | `roroDisable(id, b=true)` | Disable (or enable with `false`) | `roroDisable('age')` | | `roroEnable(id)` | Enable | `roroEnable('age')` | | `roroReadonly(id, b=true)` | Toggle readonly | `roroReadonly('age')` | | `roroRequired(id, b=true)` | Toggle the HTML `required` flag | `roroRequired('email')` | | `roroShow(id)` / `roroHide(id)` | Show / hide the whole field (wrapper) | `roroHide('coupon')` | | `roroToggleVisibility(id, b?)` | Toggle visibility | `roroToggleVisibility('coupon', true)` | | `roroFocus(id)` | Focus the control | `roroFocus('email')` | | `roroLabel(id, text?)` | Get or set the label text | `roroLabel('email', 'E-mail')` | | `roroField(id)` | Get the field handle | `roroField('email').value()` | #### Errors | Helper | Does | Example | |--------|------|---------| | `roroClearError(id)` | Hide the field's error | `roroClearError('email')` | | `roroShowError(id, msg, show=true)` | Show (or hide) an inline error + set `aria-invalid` | `roroShowError('email', 'Already taken')` | #### Events | Helper | Does | Example | |--------|------|---------| | `roroOnChange(id, fn)` | Run `fn(value, event)` when the field changes | `roroOnChange('country', v => …)` | | `roroTrigger(id, event='change')` | Dispatch an event on the control | `roroTrigger('email', 'input')` | | `roroTriggerChangeAll()` | Fire `change` on every field | `roroTriggerChangeAll()` | #### Selects | Helper | Does | Example | |--------|------|---------| | `roroAddOption(id, label, value, category?)` | Add one option (client-side) | `roroAddOption('c', 'France', 'fr', 'EU')` | | `roroAddOptions(id, list)` | Add a batch `[{label,value,category?}]` | `roroAddOptions('c', [{label:'Spain',value:'es'}])` | | `roroAddOptionsAjax(id, url, params?)` | Fetch options from your JSON endpoint → Promise | `roroAddOptionsAjax('c', '/api/countries', {q:'fr'})` | | `roroSetOptions(id, list)` | Replace **all** options | `roroSetOptions('c', list)` | | `roroRemoveOption(id, value)` | Remove one option | `roroRemoveOption('c', 'fr')` | | `roroClearOptions(id)` | Remove every option | `roroClearOptions('c')` | | `roroOptions(id)` | Read options `[{label,value,category}]` | `roroOptions('c')` | | `roroDisableSelect(id, b=true)` | Disable a custom select | `roroDisableSelect('c')` | | `roroReadonlySelect(id, b=true)` | Make a custom select readonly | `roroReadonlySelect('c')` | | `roroShowDropDown(id, show=true)` | Open / close the dropdown | `roroShowDropDown('c', true)` | #### Repeatables | Helper | Does | Example | |--------|------|---------| | `roroAddRow(id, data?)` | Append a row (optionally prefilled) | `roroAddRow('contacts', {name:'Ada'})` | | `roroRemoveRow(id, index)` | Remove the row at a position | `roroRemoveRow('contacts', 0)` | | `roroClearRows(id)` | Remove every row | `roroClearRows('contacts')` | | `roroRows(id)` | Read all rows as data | `roroRows('contacts')` | | `roroRowsCount(id)` | Number of rows | `roroRowsCount('contacts')` | | `roroRow(id, target)` | A single-row handle (by key or position) | `roroRow('contacts', 12)` | | `roroRowField(id, target, name)` | A field handle inside one row | `roroRowField('contacts', 12, 'email')` | | `roroLockRow(id, target, b=true)` | Lock/unlock a row's remove button | `roroLockRow('contacts', 12)` | Row handles (`roroRow(...)` / `roro('contacts').row(...)`) expose: `field(name)` · `fields()` · `value(data?)` · `key()` · `index()` · `remove()` · `lockRemoval(b?)` · `allowRemoval()` · `isRemovable()` · `disable(b?)` · `enable()` · `moveUp()` · `moveDown()`. #### Forms | Helper | Does | Example | |--------|------|---------| | `roroFormData(formId)` | Serialize the form to a plain object | `roroFormData('signup')` | | `roroFillForm(formId, data)` | Prefill fields (by name, then id) | `roroFillForm('signup', user)` | | `roroSubmit(formId)` | Submit (honours the AJAX button) | `roroSubmit('signup')` | | `roroResetForm(formId)` | Native reset + clear custom selects | `roroResetForm('signup')` | | `roroClearForm(formId)` | Clear every field | `roroClearForm('signup')` | | `roroValidateForm(formId)` | `reportValidity()` → bool | `roroValidateForm('signup')` | | `roroFormErrors(formId, errors)` | Show server-side errors `{field:[msg]}` | `roroFormErrors('signup', {email:['Taken']})` | | `roroClearFormErrors(formId)` | Clear all form errors | `roroClearFormErrors('signup')` | | `roroOnSuccess(formId, fn)` | AJAX success → `fn(response, event)` | `roroOnSuccess('signup', r => …)` | | `roroOnError(formId, fn)` | AJAX error → `fn(xhr, event)` | `roroOnError('signup', x => …)` | #### Overlay & low-level | Helper | Does | |--------|------| | `roroShowOverlay(show=true)` | Show/hide the form's loading overlay | | `roroSubmitButton(buttonId, formId)` | Programmatically run a submit button's flow | | `roroRegisterButtonOnClick(buttonId)` | Wire a `.roro-btn-submit` (done automatically on load) | | `roroGetWrapper(id)` | The field's wrapper element | | `populateFormErrors(form, errors)` · `clearFormErrors(form)` | The error helpers `roroFormErrors`/`roroClearFormErrors` wrap | | `addSelect(el)` · `addMultiSelect(el)` · `addRepeatable(el)` | Register a freshly-inserted component (done automatically) | > All of the above are plain `window.*` globals — no import, no namespace. The chainable equivalents live on `roro(id)` (see the previous two sections).
🪄 Laravel-native validation, old() & AJAX
RoroForm is wired into the framework, so the usual controller flow needs no front-end glue: - **Repopulation.** Every field reads `old()` automatically after a failed validation — including nested array names and repeatable rows. - **Error display.** Each field pulls its first message from `session('errors')` and shows it inline, with a red border on the control. - **CSRF.** `
🎨 Theming & publishing the views
Pick a theme once in `config/roroform.php`: Three themes ship complete, **accessible**, and component-for-component identical: | Theme | Styling | Dark mode trigger | |-------|---------|-------------------| | `tailwind` | TailwindCSS utility classes | `dark:` utilities — `.dark` class (or `prefers-color-scheme`, per your Tailwind `darkMode` config) | | `bootstrap` | Bootstrap 5 classes | `data-bs-theme="dark"` on an ancestor | | `raw` | **Framework-free** — no Tailwind, no Bootstrap. Injects its own small stylesheet automatically, so you get a clean, modern look on **any** page with **zero CSS framework**. | `.dark` class or `data-roro-theme="dark"` on an ancestor | **All three themes are fully dark-mode aware** — every component remaps colors, borders, backgrounds and contrast in dark mode. Each is driven by a class/attribute on an ancestor (usually ``), so you stay in control. To follow the OS setting, map it once: Test it on the playground — click the **Dark mode** toggle to switch. The `raw` theme is themed with CSS custom properties — override `--roro-accent`, `--roro-radius`, `--roro-border`, … to restyle it, or publish the stylesheet: Every theme is accessible: proper `
🔧 Arbitrary HTML attributes pass-through
Every input forwards **arbitrary HTML attributes** (the Blade attribute bag) onto the underlying element — no need to declare them in PHP. Great for `data-*`, `aria-*`, `autocomplete`, `maxlength`, Alpine `x-` directives, etc. renders `... data-testid="email" autocomplete="email" maxlength="120">` on the ``. Custom CSS classes go through the dedicated `class` attribute and are merged with the component's own `roro-*` classes.
Requirements
| PHP | ^8.0 |
| Laravel | 9, 10, 11, 12, 13 |
| JavaScript | none — the runtime is dependency-free vanilla JS |
| CSS | Tailwind, Bootstrap, or none — the raw theme ships its own stylesheet |
Installation recap
Upgrading
Coming from the 1.x (jQuery) runtime? 2.0 drops jQuery for dependency-free vanilla JS. The everyday facade is unchanged; a few front-end event/return contracts changed — see the CHANGELOG for the short migration guide.
License
Apache-2.0.
All versions of roroform with dependencies
illuminate/support Version ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0
illuminate/view Version ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0