Skip to content

Schema form dialog ​

A create or edit form in a modal, from the same field list the page version uses.

Open in demo
Open in demo

Usage ​

It is a wrapper around SchemaForm, not a second implementation. Zod generation, visibleIf, server-error placement and the field grid all come from the same place, so the modal and the page cannot drift apart. See Schema for the field list and Forms for the engine.

v-model is the form's values and v-model:open is the dialog. submit carries the validated values; the dialog does not close itself, because whether it should depends on whether your request succeeded.

When a modal, and when a page ​

The rule the template follows: a create is a modal when its schema has no sections, meaning one group or none. Two or more sections stays a carded page.

That is not arbitrary. A schema author reaches for a second group exactly when the record has enough to it to need organizing, which is the same moment a dialog stops being the right container. Today that makes categories and roles modals, and the other modules pages.

The form inside ​

The dialog provides a form surface that is flat and footerless: one two-column grid, no section headings, and no footer of the form's own, because the dialog already owns one. Without that you would get two Cancel buttons.

Examples ​

Server errors ​

serverErrors takes { field, message }[] straight from a 422 and lands each message on its field, exactly as a page does.

API ​

Source: src/shared/components/common/SchemaFormDialog.vue

Props ​

PropTypeDefaultDescription
openbooleanrequiredYou own it.
titlestringrequiredThe dialog's heading.
descriptionstring—Visually hidden. Present so the dialog has an accessible description.
schemaFieldDefinition[]requiredThe same field list a page would use.
modelValueRecord<string, unknown>{}The form's values.
submittingbooleanfalseDisables the submit button and swaps its label.
submitLabelstring'common.save'Defaults to "Save".
submittingLabelstring'common.saving'Defaults to "Saving…".
serverErrors{ field: string, message: string }[]—Forwarded to the form.

Emits ​

EventPayloadDescription
update:openvalue: booleanFrom Cancel, Escape and the overlay.
update:modelValuevalues: Record<string, unknown>As the reader types.
submitvalues: Record<string, unknown>After validation passes.

Slots ​

None.

Accessibility ​

  • Focus moves into the dialog, Tab is trapped inside it, and Escape closes it.
  • The description is kept in the DOM as visually hidden text, so the dialog has an accessible description without a second line of copy under every title.
  • The footer is a two-column grid of equal-width buttons with Save as the primary, and it uses logical margins, so it mirrors in a right-to-left layout.
  • The content scrolls inside the dialog at up to 90% of the viewport height, so a long form never pushes its own footer off the screen.