@xenterprises/nuxt-x-app-formkit
v0.3.0
Published
Nuxt layer providing FormKit DX enhancements for admin dashboards — CRUD modal/slide components, custom file upload input, and dynamic FormKit Pro support.
Readme
@xenterprises/nuxt-x-app-formkit
Nuxt layer providing FormKit DX enhancements for admin dashboards — CRUD modal/slide components, custom file upload input, and dynamic FormKit Pro support.
Install
npm add @xenterprises/nuxt-x-app-formkitSetup
// nuxt.config.ts
export default defineNuxtConfig({
extends: ["@xenterprises/nuxt-x-app-formkit"],
});FormKit Theme
This layer does not include a FormKit theme. You must provide your own formkit.theme.ts.
See .playground/formkit.theme.ts for an example theme. Reference it in your own formkit.config.ts, spreading the layer's xFormkitConfig to keep the fileUpload input and genesis icons (a consumer config file replaces the layer's config entirely):
// formkit.config.ts
import { defineFormKitConfig } from "@formkit/vue";
import { xFormkitConfig } from "@xenterprises/nuxt-x-app-formkit/formkit.config";
import { rootClasses } from "./formkit.theme";
export default defineFormKitConfig({
...xFormkitConfig,
config: { rootClasses },
});// nuxt.config.ts
export default defineNuxtConfig({
formkit: { configFile: "./formkit.config.ts" },
});For Tailwind CSS v4, add to your main CSS:
@source "./formkit.theme.ts";FormKit Pro (Optional)
Set the FORMKIT environment variable to enable Pro inputs:
FORMKIT=fk-your-pro-keyThe layer loads FormKit Pro automatically when the key starts with fk- and @formkit/pro is installed. The key must be present at build time — the layer picks a Pro-enabled config file during the Nuxt build (the FormKit config cannot be async, so Pro cannot be loaded dynamically at runtime).
Components
XFormModal
Modal dialog with FormKit integration for CRUD operations.
<XFormModal
v-model="showModal"
endpoint="/api/users"
@success="handleSuccess"
>
<FormKit type="text" name="name" label="Name" validation="required" />
<FormKit type="email" name="email" label="Email" validation="required|email" />
</XFormModal>Props
| Name | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| modelValue | boolean | false | No | Controls modal visibility (v-model) |
| title | string | "" | No | Modal title. Auto-generated from endpoint if omitted |
| size | "sm" \| "md" \| "lg" \| "xl" \| "full" | "md" | No | Modal width |
| closable | boolean | true | No | Show close button in header |
| loading | boolean | false | No | External loading state |
| disabled | boolean | false | No | Disable form submission |
| submitText | string | "Save" | No | Submit button text. Shows "Create"/"Update" when using endpoint |
| cancelText | string | "Cancel" | No | Cancel button text |
| confirmClose | boolean | false | No | Require confirmation before closing |
| endpoint | string | — | No | API endpoint for CRUD operations |
| recordId | string \| number | — | No | Record ID for edit mode (triggers GET on open) |
| initialData | Record<string, any> | — | No | Pre-fill form data |
| method | "POST" \| "PUT" \| "PATCH" | Auto | No | HTTP method. Auto-selects POST (create) or PUT (edit) |
| idKey | string | "id" | No | Key used to identify records |
| headers | Record<string, string> \| () => Record<string, string> | — | No | Custom request headers |
| transform | (data) => data | — | No | Transform data before submission |
| showToast | boolean | true | No | Show success/error toasts |
| closeOnSuccess | boolean | true | No | Close modal on successful submission |
| resetOnClose | boolean | true | No | Reset form when modal closes |
| formId | string | Auto | No | Custom FormKit form ID |
| onSuccess | "refresh" \| "navigate" \| function | — | No | Action after successful submission |
| navigateTo | string | — | No | Route to navigate to (use with onSuccess="navigate") |
Events
| Event | Payload | Description |
|-------|---------|-------------|
| update:modelValue | boolean | Modal visibility changed |
| submit | — | Submit clicked (legacy mode, no endpoint) |
| success | (data, response) | CRUD operation succeeded |
| error | (error) | CRUD operation failed |
| loaded | (data) | Record loaded by ID |
| close | — | Close button clicked |
Slots
| Slot | Props | Description |
|------|-------|-------------|
| default | { state, disabled, data, isEdit } | Form content |
| header | — | Custom header content |
| footer | { loading, disabled } | Custom footer with action buttons |
| loading | — | Custom loading indicator |
| error | { error, retry } | Custom error state |
Exposed Methods
| Method | Description |
|--------|-------------|
| open(data?) | Open modal, optionally pre-filling data |
| close() | Close modal |
| reset() | Reset form to initial state |
| submit() | Trigger form submission |
XFormSlide
Slide-over panel with FormKit integration for CRUD operations. Same props/events as XFormModal, plus:
| Name | Type | Default | Description |
|------|------|---------|-------------|
| side | "left" \| "right" | "right" | Panel position |
| size | "sm" \| "md" \| "lg" \| "xl" | "md" | Panel width (320/400/500/600px) |
<XFormSlide
v-model="showSlide"
endpoint="/api/settings"
:initial-data="{ id: 1, key: 'theme', value: 'dark' }"
method="PATCH"
side="right"
@success="handleSuccess"
>
<FormKit type="text" name="key" label="Key" validation="required" />
<FormKit type="text" name="value" label="Value" validation="required" />
</XFormSlide>XFormFileUpload
Drag-and-drop file upload component.
<XFormFileUpload
v-model="files"
accept="image/*"
:multiple="true"
:max-size="5 * 1024 * 1024"
:max-files="3"
:upload-handler="handleUpload"
/>Props
| Name | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| modelValue | File \| File[] \| null | null | No | Selected file(s) |
| multiple | boolean | false | No | Allow multiple file selection |
| accept | string | "*" | No | Accepted file types (e.g., "image/*", ".pdf,.doc") |
| acceptLabel | string | "" | No | Human-readable label for accepted types |
| maxSize | number | — | No | Maximum file size in bytes |
| maxFiles | number | — | No | Maximum number of files (multiple mode) |
| disabled | boolean | false | No | Disable the upload |
| dropzone | boolean | true | No | Enable drag-and-drop |
| uploadHandler | (files: File[]) => Promise<void> | — | No | Custom upload handler called after file selection |
Events
| Event | Payload | Description |
|-------|---------|-------------|
| update:modelValue | File \| File[] \| null | File selection changed |
| error | string | Validation error (size, count) |
Exposed Methods
| Method | Description |
|--------|-------------|
| clear() | Clear all selected files |
| openFilePicker() | Open the native file picker |
FormKit File Upload Input
Use as a FormKit input type:
<FormKit
type="fileUpload"
name="avatar"
label="Profile Picture"
accept="image/*"
:max-size="5 * 1024 * 1024"
help="Upload a profile picture"
/>Supports all XFormFileUpload props as FormKit input props.
Composables
useFormCrud
Shared CRUD logic used internally by XFormModal and XFormSlide. Available for building custom form containers.
const crud = useFormCrud(options, callbacks, externalLoading?, externalDisabled?)Returns: { isLoading, isSubmitting, loadError, formData, formKey, isEditMode, combinedLoading, combinedDisabled, loadRecord, initializeForm, handleFormKitSubmit, resetForm, computeTitle, computeSubmitText }
Environment Variables
| Name | Required | Description |
|------|----------|-------------|
| FORMKIT | No | FormKit Pro license key. Must start with fk- to activate Pro inputs |
Error Reference
| Error | Source | Description |
|-------|--------|-------------|
| [nuxt-x-formkit] FORMKIT env var is set but does not start with 'fk-' | Startup | Invalid Pro key format |
| [nuxt-x-formkit] FORMKIT env var is set but @formkit/pro is not installed | Startup | Missing @formkit/pro package |
| Failed to load record | CRUD load | GET request to endpoint/recordId failed |
| Created successfully / Updated successfully | CRUD submit | Success toast |
| Operation failed | CRUD submit | Generic submission error |
How It Works
The layer registers @formkit/nuxt with auto-import and a shared, synchronous formkit.config.ts (@formkit/nuxt never awaits the config, so it must be sync). At build time, the layer checks the FORMKIT env var and whether @formkit/pro is installed, and points FormKit at formkit.config.pro.ts instead when both are present — that file statically imports @formkit/pro and reads the key from runtime config at startup.
XFormModal and XFormSlide both consume the useFormCrud composable, which handles:
- Detecting create vs. edit mode (from
recordIdorinitialData) - Loading records by ID with error/retry states
- Building the correct HTTP method and URL
- Transforming payloads before submission
- Showing toast notifications
- Handling success behaviors (refresh, navigate, custom function)
The XFormFileUpload component provides a standalone drag-and-drop upload with validation. It also registers as a custom FormKit input (type="fileUpload") via the createInput API, bridging FormKit context with the Vue component.
Layer Architecture
nuxt.config.ts— Registers@formkit/nuxtmodule, resolves which FormKit config file to use (Pro detection at build time), exposesFORMKITruntime config.formkit.config.ts— Configures FormKit with genesis icons and the customfileUploadinput; exportsxFormkitConfigfor consumer config composition.formkit.config.pro.ts— Pro variant (static@formkit/proimport), selected only when a validFORMKITkey is set and@formkit/prois installed.app/components/— Auto-imported components (XFormModal, XFormSlide, XFormFileUpload).app/composables/— Auto-imported composables (useFormCrud).app/formkit/inputs/— Custom FormKit input definitions.
Consumer apps should provide their own formkit.theme.ts and can override the FormKit config via Nuxt's formkit.configFile option — spread xFormkitConfig (exported from the layer's formkit.config.ts) into it to keep the fileUpload input and genesis icons.
Usage with nuxt-x-app
For full admin dashboard functionality, use both layers:
export default defineNuxtConfig({
extends: [
"@xenterprises/nuxt-x-app",
"@xenterprises/nuxt-x-app-formkit",
],
});Development
npm install
npm run dev # Start playground
npm run test # Run tests
npm run dev:prepare # Prepare types