DXForm

DXForm

The canonical form renderer. Drive it with field definitions; add tabs when you need a multi-tab editor. Flat and tabbed forms share one engine (DXField), so every field type, conditional field, per-field slot override, async option, and nested repeater works the same in both.

It works with the fetch-based useForm composable (no Inertia required) and accepts either a useForm return or a defineForm return (with the latter, fields may be omitted).

Live Examples

Product Editor

A tabbed form with conditional fields, a currency input, and auto-switching to the tab containing the first validation error.

Name
Price
£
SKUFormat: ABC-123
Stock on hand
Horizontal layout

Setting layout="horizontal" moves every field's label to a left-hand column, including checkbox and repeater fields. A field can opt back into vertical with field.layout.

Full name
Email
Subscribe to updates

Props

Name Click to sort ascendingType Required Click to sort ascendingDefault Description
formUseFormReturn<any> | DefineFormReturn<any>Yes-Form instance from useForm, or the { form, fields } object from defineForm.
fieldsFieldDefinition[]No-Field definitions. Optional when form is a defineForm return (taken from it).
tabsFormTab[]No-Tab definitions. When omitted, a flat single-column form renders.
contextRecord<string, any>No-Extra context merged under the live form data when evaluating predicates (label/hint/when/disabled).
activeTabnumber (v-model)No0Active tab index. Two-way bindable via v-model:active-tab.
autoErrorTabbooleanNotrueAuto-switch to the first tab containing a validation error.
submitTextstringNoSubmitSubmit button text.
submitLoadingTextstringNoSubmitting...Submit button loading text.
showSubmitbooleanNotrueShow the submit button.
cardbooleanNofalseWrap a FLAT form in a card for a visual boundary (mirrors DXTable's card prop). Also forces the tabbed card on even when cardTabs is false. Off by default since DXForm is commonly embedded in a page card or modal already.
cardTabsbooleanNotrueWrap a TABBED form's content in a card panel so the active tab reads as a finished panel connected to the tab strip (the standard Bootstrap card-with-tabs pattern) rather than floating on the bare page. Set false for bare tabs (e.g. inside a modal that already provides a boundary). Ignored for flat forms — use card for those.
layout"vertical" | "horizontal" | "auto"No"vertical"Form-wide field layout. "horizontal" puts the label to the left of the input, always. "auto" picks horizontal or vertical from the width of the form's own container (see Container-driven layout below). Overridable per-field via field.layout.
layoutThresholdnumberNo640Container width (px) at or above which layout: "auto" goes horizontal. Ignored for the explicit layouts.
labelColsnumber | { sm?, md?, lg?, xl? }No3Label column width for horizontal layout (mirrors BFormGroup's labelCols). Overridable per-field via field.labelCols. Omitted breakpoints collapse to vertical below that width.

Events

Name Click to sort ascendingParameters Description
submitnoneEmitted when the form is submitted (run your form.post/put in the handler).

Slots

Per-field slots are keyed by field key, e.g. #value(price) or #hint(sku).

#value vs #span: a labelled custom control is a normal field plus a #value(<key>) slot — it keeps the form’s label column and grid, no re-derived col classes. Reserve #span(<key>) (with span: true) for genuinely full-width, label-less blocks (a sub-table, an activity log).

Name Click to sort ascendingDescription Scoped Props
value(<key>)Replace a field's input control with custom markup.-
span(<key>)Full-width custom block for a field marked span: true.-
info(<key>)Rich info block rendered always-visible below the field (not the label popover).-
info-popover(<key>)Rich body for the label’s hover/focus info popover (lists, bold, paragraphs). Overrides the plain field.info text.-
hint(<key>)Override the hint text rendered below the field.-
repeater-row(<key>)Custom row layout for a repeater field.-
switch-list-item(<key>)Extra content beside a switch-list row's toggle (e.g. a notes input bound to your own model).-
field(<key>)Fully replace a field's rendering (including its label), bypassing DXField entirely. Supersedes field-before/field-after for the same key.-
field-before(<key>)Content rendered directly above a field (e.g. a quick-create button above a select).-
field-after(<key>)Content rendered directly below a field.-
tab-content(<key>)Replace the entire body of a tab.-
tab-before(<key>)Content rendered before a tab's fields.-
tab-after(<key>)Content rendered after a tab's fields.-
footerContent rendered after the form body.-

Container-driven layout

layout="horizontal" is unconditional, and that is a problem on a dashboard: Bootstrap’s breakpoints are media queries, so they only ever see the window. A page narrowed by the persistent sidebar — or a form inside a modal — can have very little room while the viewport is wide, and no breakpoint fires. The label/input split then cramps the labels and truncates the controls.

layout="auto" measures the form’s own container instead and stacks to vertical below layoutThreshold:

<DXForm :form="form" :fields="fields" layout="auto" />

The default threshold is 640px. With the default 3-column label, the label’s text area is roughly containerWidth / 4 - 18px, so 640 leaves it ~142px — enough for a 20-character label such as “Unit price (ex VAT)” to stay on one line, with the control column still ~474px. Raise layoutThreshold if your labels run longer, or lower labelCols instead.

Notes:

  • vertical (the default) and horizontal are unchanged and unconditional. No existing form changes appearance; auto is opt-in.
  • Before the first measurement — and when server-rendering — the form renders vertical. A stacked form is legible at any width, so it is the safe thing to show when the width is not yet known.
  • The crossing has a small hysteresis band, so a form that gains a scrollbar when it stacks cannot flip back and forth at the boundary.
  • Per-field field.layout still wins, and a span: true field is always full-width.

The measurement itself is available on its own — see useContainerWidth if you need to drive something else (a table that becomes cards, a column that collapses) off the same signal.

Field definitions

Each field is a FieldDefinition. Supported type values:

TypeRenders
text, email, password, number, url, tel, date, datetime-local, datetime, timeNative input
textareaMulti-line input
selectDropdown (sync options or async optionsLoader). Add searchable: true for a type-to-filter select that still models the option’s value
autocompleteFree-text input with a <datalist> of suggestions (sync options or async optionsLoader); accepts values not in the list
radioRadio group (single value from options)
checkboxSingle checkbox (boolean)
checkbox-groupMultiple checkboxes — “pick any of N” — from options; the model is an array of the checked option values (default the field to [])
switch-listA list of labelled toggle rows (allergens, feature flags, opt-ins) from options — each row a real form-grid group with a compact track switch. Same array model as checkbox-group. switchVariant: 'neutral' is usually right for lists (a wall of red-off toggles over-signals); per-row extras via #switch-list-item(<key>)
currency, percentageNumeric input with a £/% affix
image, fileFile input (image shows a preview)
componentRenders your field.component (escape hatch)
repeaterNested, repeatable sub-form (see DXRepeater)

Common options beyond key/type/label:

Name Click to sort ascendingType Required Click to sort ascendingDefault Description
label / hintstring | (model) => stringNo-Static or model-derived label and hint text.
infostring | (model) => stringNo-PLAIN-TEXT help shown in the label's hover/focus popover — markup does not render, so links and lists are lost. For rich help (links, bulleted lists, bold), use the #info-popover(<key>) slot instead (or #info(<key>) for an always-visible block below the field).
whenboolean | (model) => booleanNotrueConditional visibility, evaluated reactively against the live model.
readonlyboolean | (model) => booleanNofalseRender the field read-only — still a bordered, non-editable control.
plaintextboolean | (model) => booleanNofalseDisplay the value as static text instead of a control (no border, no input box). Implies read-only for every type: anything backed by a real input (text family, textarea, currency, percentage, autocomplete) renders as static text; select/radio/checkbox/switch/file are disabled instead.
revealablebooleanNotrueOnly for type: "password" — show the reveal (eye) toggle that switches the input between masked and plain text. Set false for a bare password input.
disabled / disabledWhenboolean | (model) => booleanNofalseDisable the field, statically or from the model.
searchablebooleanNofalseFor a select field: type to filter a long option list while the model still holds option.value (an id). Use for hundreds of options — a plain select is unusable at that size, and the autocomplete type models the typed text, so it cannot back a foreign key
options / optionsLoaderFieldOption[] | (model) => Promise<FieldOption[]>No-Select/radio options, synchronous or async. Set reloadOptionsOnChange to refetch on model change.
submitbooleanNotrueInclude the field's value in the submitted payload. Set false for a PRESENTATIONAL field (a header, an alert, an explanatory block via span) that lays the form out but holds no data — DXTable's edit modal seeds its form from every editFields key, so those would otherwise be POSTed alongside the real ones
spanbooleanNofalseRender full-width, delegating content to the #span(<key>) slot. Also always bypasses horizontal layout's column split.
layout / labelCols"vertical" | "horizontal" / number | { sm?, md?, lg?, xl? }No-Per-field override of DXForm's layout/labelCols props.
currencySymbol / accept / step / min / maxstring | numberNo-Type-specific options (currency symbol, file accept, numeric bounds).

A FormTab is { key, label?, fieldKeys, when?, lazy? }. Tabs whose when is false — or that have no visible fields and no custom tab slot — are hidden automatically.

Extended Component

This is a custom component that extends beyond simple Bootstrap Vue Next wrappers, providing additional functionality specific to Laravel dashboards.