@eidhachem/forms-nuxt-ui
v0.1.1
Published
Official @eidhachem/forms UI adapter for Nuxt UI: maps built-in field types to Nuxt UI components.
Maintainers
Readme
@sowlutions/forms-nuxt-ui
Status: Phase 13 complete.
createNuxtUiAdapter()maps 22 of@sowlutions/forms's built-in field types onto real Nuxt UI v4 components. See ADR-018 for the full per-type breakdown, and for why this package never imports from@nuxt/uiitself — you hand it your own already-resolved component references (one small one-time setup step, below).
Setup
Nuxt UI v4's components internally rely on Nuxt-build-time-only virtual
modules (#build/ui/*, #imports), so they can only be imported from code
that a running Nuxt build actually compiles — never from this package's own
ahead-of-time build. Instead, pull your already-resolved component
references from Nuxt's #components virtual module (the same mechanism
Nuxt's own auto-imports are built on) and hand them to
createNuxtUiAdapter() once, in a plugin:
// app/plugins/forms-nuxt-ui.ts
import {
UFormField,
UInput,
UTextarea,
UInputNumber,
USlider,
USelectMenu,
URadioGroup,
UCheckbox,
UCheckboxGroup,
USwitch,
UInputTags,
UColorPicker,
UInputRating,
UPinInput,
UFileUpload,
} from '#components';
import { createNuxtUiAdapter } from '@sowlutions/forms-nuxt-ui';
import { registerAdapter, setDefaultAdapter } from '@sowlutions/forms';
export default defineNuxtPlugin(() => {
registerAdapter(
createNuxtUiAdapter({
UFormField,
UInput,
UTextarea,
UInputNumber,
USlider,
USelectMenu,
URadioGroup,
UCheckbox,
UCheckboxGroup,
USwitch,
UInputTags,
UColorPicker,
UInputRating,
UPinInput,
UFileUpload,
}),
);
setDefaultAdapter('nuxt-ui');
});From then on, FormRenderer/FieldRenderer (or your own headless markup)
render every mapped field type through the real Nuxt UI component — no
further wiring per form or per field.
Supported field types
text, password, email, tel, url, search, textarea, number,
currency, percentage, range, select, multiselect, autocomplete,
combobox, radio, checkbox, checkbox-group, switch, tags,
color, rating, otp, file, image, file-list, image-list.
Deliberately unmapped — supports() returns false and
resolveFieldComponentOrThrow throws a clear error rather than rendering
silently-wrong UI (rule 11): phone (no native Nuxt UI component);
date/datetime/time/month/week/date-range (Nuxt UI's date/time
inputs take @internationalized/date value objects, not a plain JS value —
see ADR-018 for why this adapter doesn't attempt that conversion
generically); heading/description/divider/section/group/tabs/
accordion (structural, not value-bound fields); custom (bypasses
adapter resolution entirely, ADR-013 §3).
select/multiselect/autocomplete/combobox submit just the selected
option's value (not the whole { label, value } object) — this needs
valueKey: 'value' on Nuxt UI's USelectMenu, since (unlike
radio/checkbox-group) it doesn't default to that itself; set for you
already. otp submits a single string — UPinInput's own modelValue is
always a per-digit array, converted both ways. Neither needs anything
extra from you; see ADR-025 if you're curious why they needed fixing.
Passing per-field config (options, accept, formatOptions, ...)
Two channels reach the real Nuxt UI component, both forwarded through
(FormAdapter.resolveComponent only ever sees the field type string):
field.props (a loose bag, works for anything), and the field's own typed
per-type config (SelectionFieldConfig.options, CurrencyFieldConfig.currency,
FileFieldConfig.accept, ... — packages/core/src/types/field.ts's
FieldTypeConfigMap) — declared as ordinary top-level FieldSchema
properties, not nested under props. props always wins on a key
collision (see ADR-023), so it's also how to override the typed config for
one render without touching the schema. Both forms work:
{ name: 'country', type: 'select', options: [{ label: 'USA', value: 'us' }] }
{ name: 'country', type: 'select', props: { options: [{ label: 'USA', value: 'us' }] } } // equivalent
{ name: 'price', type: 'currency', currency: 'USD' }
{ name: 'avatar', type: 'image', props: { accept: '.png,.jpg' } } // overrides the 'image/*' defaultoptions is renamed to Nuxt UI's own items prop internally — @sowlutions/forms'
SelectOption shape (label/value/disabled/description) already
matches Nuxt UI's default item shape. One exception: a MaybeReactive
callback or AsyncOptionsConfig options (Phase 6 dynamic forms,
useFieldOptions()) is not merged automatically — only a plain, already-
resolved array is; resolve it via useFieldOptions() yourself and pass the
result through field.props.options.
See the repository root README and docs/architecture for the full architecture and roadmap.
