nuxt-x-app-formkit
nuxt-x-app-formkit
FormKit-powered form containers with modal and slideover variants, plus a drag-and-drop file upload component. All form containers support both a CRUD mode (pass an endpoint prop and the component handles fetch, submit, validation, and toast notifications automatically) and a legacy mode (omit endpoint and handle submission yourself via the @submit event).
Installation
npm install @xenterprises/nuxt-x-formkit
// nuxt.config.ts
export default defineNuxtConfig({
extends: ['@xenterprises/nuxt-x-formkit']
})
Components
| Component | Description |
|---|---|
XFormModal | FormKit form in a UModal overlay |
XFormSlide | FormKit form in a USlideover panel |
XFormFileUpload | Drag-and-drop file picker with validation |
XFormModal
A modal dialog wrapping a FormKit form. In CRUD mode (endpoint provided) the component fetches an existing record by recordId, runs FormKit validation on submit, posts/puts to the API, shows a toast, and optionally refreshes or navigates on success.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | boolean | false | Controls modal open state (v-model) |
title | string | "" | Modal header title. Auto-derived from endpoint when omitted |
size | "sm" | "md" | "lg" | "xl" | "full" | "md" | Max-width of the modal content |
closable | boolean | true | Show the X close button in the header |
loading | boolean | false | External loading state merged into button disabled/spinner |
disabled | boolean | false | Disable the submit button |
submitText | string | "Save" | Submit button label (auto becomes "Create" / "Update" in CRUD mode) |
cancelText | string | "Cancel" | Cancel button label |
confirmClose | boolean | false | Suppress auto-close on X click; handle @close yourself |
endpoint | string | — | API base path (e.g. /api/users). Enables CRUD mode |
recordId | string | number | — | ID of the record to fetch and edit |
initialData | Record<string, any> | — | Pre-populate form without fetching from API |
method | "POST" | "PUT" | "PATCH" | auto | HTTP method override (defaults to POST for create, PUT for edit) |
idKey | string | "id" | Key used to detect and append record ID to the URL |
headers | Record<string,string> | () => Record<string,string> | — | Extra request headers, or a function returning them |
transform | (data) => data | — | Transform form data before it is sent to the API |
showToast | boolean | true | Show success/error toasts via useToast() |
closeOnSuccess | boolean | true | Close modal after a successful API call |
resetOnClose | boolean | true | Clear form data when the modal closes |
formId | string | auto | Override the generated FormKit form node ID |
onSuccess | "refresh" | "navigate" | Function | — | Post-success action: refresh Nuxt data, navigate to a route, or custom callback |
navigateTo | string | — | Route template for onSuccess: "navigate" (supports :id / {id}) |
Emits
| Event | Payload | Description |
|---|---|---|
update:modelValue | boolean | v-model sync |
submit | — | Fired in legacy mode (no endpoint) when the submit button is clicked |
close | — | Fired when the X or Cancel button is clicked |
success | (data, response) | Fired after a successful API call |
error | (error) | Fired when the API call fails |
loaded | (data) | Fired after the record is fetched by recordId |
Exposed Methods
| Method | Description |
|---|---|
open(data?) | Open the modal and optionally pre-populate form data |
close() | Close the modal |
reset() | Re-initialize form data |
submit() | Programmatically trigger form submission |
Usage
CRUD mode — create a new user:
<template>
<UButton @click="open = true">New User</UButton>
<XFormModal
v-model="open"
endpoint="/api/users"
:on-success="'refresh'"
>
<template #default="{ isEdit }">
<FormKitText name="name" label="Name" validation="required" />
<FormKitText name="email" label="Email" validation="required|email" />
</template>
</XFormModal>
</template>
<script setup>
const open = ref(false)
</script>
CRUD mode — edit an existing record:
<XFormModal
v-model="open"
endpoint="/api/users"
:record-id="user.id"
on-success="refresh"
/>
Legacy mode — handle submit yourself:
<XFormModal v-model="open" title="Custom Form" @submit="handleSubmit">
<input v-model="form.name" />
</XFormModal>
XFormSlide
Identical feature set to XFormModal but rendered as a USlideover panel. Supports the same CRUD props and success behaviors.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | boolean | false | Controls slideover open state (v-model) |
title | string | "" | Panel header title. Auto-derived from endpoint when omitted |
side | "left" | "right" | "right" | Which edge the panel slides from |
size | "sm" | "md" | "lg" | "xl" | "md" | Panel width (sm=320px, md=400px, lg=500px, xl=600px) |
loading | boolean | false | External loading state |
disabled | boolean | false | Disable the submit button |
submitText | string | "Save" | Submit button label |
cancelText | string | "Cancel" | Cancel button label |
confirmClose | boolean | false | Suppress auto-close; handle @close yourself |
endpoint | string | — | API base path. Enables CRUD mode |
recordId | string | number | — | ID of the record to fetch and edit |
initialData | Record<string, any> | — | Pre-populate form without fetching from API |
method | "POST" | "PUT" | "PATCH" | auto | HTTP method override |
idKey | string | "id" | Key used to detect and append record ID |
headers | Record<string,string> | () => Record<string,string> | — | Extra request headers |
transform | (data) => data | — | Transform form data before sending |
showToast | boolean | true | Show success/error toasts |
closeOnSuccess | boolean | true | Close panel after successful API call |
resetOnClose | boolean | true | Clear form data when the panel closes |
formId | string | auto | Override the generated FormKit form node ID |
onSuccess | "refresh" | "navigate" | Function | — | Post-success action |
navigateTo | string | — | Route template for onSuccess: "navigate" |
Emits
| Event | Payload | Description |
|---|---|---|
update:modelValue | boolean | v-model sync |
submit | — | Fired in legacy mode |
close | — | Fired when closed |
success | (data, response) | Fired after successful API call |
error | (error) | Fired on API error |
loaded | (data) | Fired after record fetch |
Exposed Methods
| Method | Description |
|---|---|
open(data?) | Open the panel, optionally pre-populate |
close() | Close the panel |
reset() | Re-initialize form data |
submit() | Programmatically trigger submission |
Usage
<template>
<UButton @click="slide = true">Edit Profile</UButton>
<XFormSlide
v-model="slide"
endpoint="/api/profile"
:record-id="user.id"
side="right"
size="lg"
on-success="refresh"
>
<FormKitText name="displayName" label="Display Name" validation="required" />
<FormKitText name="bio" label="Bio" />
</XFormSlide>
</template>
<script setup>
const slide = ref(false)
</script>
XFormFileUpload
A drag-and-drop file upload zone. Emits selected files as a File or File[] via v-model. Optionally calls an async uploadHandler immediately after files are selected.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | File | File[] | null | null | Selected file(s) (v-model) |
multiple | boolean | false | Allow multiple file selection |
accept | string | "*" | Native accept attribute passed to <input type="file"> |
acceptLabel | string | "" | Human-readable label shown beneath the upload icon (falls back to accept value) |
maxSize | number | — | Maximum file size in bytes; emits @error if exceeded |
maxFiles | number | — | Maximum number of files when multiple is true |
disabled | boolean | false | Disable all interactions |
dropzone | boolean | true | Enable drag-and-drop |
uploadHandler | (files: File[]) => Promise<void> | — | Called automatically after files are selected; shows uploading spinner |
Emits
| Event | Payload | Description |
|---|---|---|
update:modelValue | File | File[] | null | v-model sync |
error | string | Validation error message (size exceeded, too many files, upload failure) |
Exposed Methods
| Method | Description |
|---|---|
clear() | Remove all selected files |
openFilePicker() | Programmatically open the OS file picker |
Usage
Basic single file:
<template>
<XFormFileUpload
v-model="file"
accept="image/*"
accept-label="PNG, JPG, GIF up to 5 MB"
:max-size="5 * 1024 * 1024"
@error="toast.add({ title: $event, color: 'error' })"
/>
</template>
<script setup>
const file = ref(null)
</script>
Multiple files with upload handler:
<XFormFileUpload
v-model="files"
multiple
accept=".pdf,.docx"
:max-files="5"
:upload-handler="uploadToS3"
/>
AI Context
package: "@xenterprises/nuxt-x-formkit"
version: "0.1.0"
components:
- XFormModal
- XFormSlide
- XFormFileUpload
crud-mode: pass `endpoint` prop; component handles fetch/submit/toast/close automatically
legacy-mode: omit `endpoint`; handle @submit event yourself
success-behaviors: onSuccess="refresh" | "navigate" | custom function
form-engine: FormKit (validation, node.submit(), node.setErrors())
ui-primitives: UModal, USlideover (Nuxt UI)
file-upload: v-model File | File[]; optional uploadHandler async function
common-pattern: |
<XFormModal v-model="open" endpoint="/api/resource" :record-id="id" on-success="refresh">
<FormKitText name="field" label="Field" validation="required" />
</XFormModal>
