Skip to content

feat(components): refactor form to tanstack#2255

Draft
dargmuesli wants to merge 1 commit intobetafrom
feat/components/form-refactor
Draft

feat(components): refactor form to tanstack#2255
dargmuesli wants to merge 1 commit intobetafrom
feat/components/form-refactor

Conversation

@dargmuesli
Copy link
Copy Markdown
Member

@dargmuesli dargmuesli commented Apr 7, 2026

This pull request migrates the form handling in the codebase from Vuelidate/Vee-Validate to the new @tanstack/vue-form library and removes all legacy validation libraries and related dependencies. It also updates the implementation of several form components to use the new validation approach, resulting in significant code simplification and modernization. Additionally, the PR updates the pnpm-lock.yaml file to reflect these dependency changes.

Resolves #1588
Resolves #1672

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates the app’s form handling away from Vuelidate/Vee-Validate to @tanstack/vue-form + Zod, removing legacy validation wrappers/components and introducing a new field UI composition layer for consistent form layout and error rendering.

Changes:

  • Add @tanstack/vue-form dependency and remove vuelidate/vee-validate packages and related Nuxt transpilation entries.
  • Refactor multiple form components (auth, support, contact/event forms, preferences) to TanStack Form APIs + Zod schemas.
  • Introduce new scn/field + scn/separator primitives and new form utilities (isFieldInvalid, normalizeFieldErrors) while removing legacy AppForm/FormInput*/scn/form helpers.

Reviewed changes

Copilot reviewed 67 out of 68 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/package.json Add TanStack Form; remove legacy validators
src/nuxt.config.ts Remove legacy deps from transpile list
src/app/utils/validation.ts Remove Vuelidate exports; keep API-based validators
src/app/utils/form.ts Add TanStack field meta helpers
src/app/pages/guest/view/[id]/index.vue Replace FormInputStateInfo with plain text
src/app/pages/event/view/[username]/[event_name]/attendance.vue Replace FormInputStateInfo with plain text
src/app/pages/account/create/index.vue Capture registration values via submit payload
src/app/composables/formSubmit.ts Remove vee-validate submit composable
src/app/composables/form.ts Remove Vuelidate-based useAppForm
src/app/components/scn/separator/Separator.vue New separator primitive (reka-ui wrapper)
src/app/components/scn/separator/index.ts Export separator primitive
src/app/components/scn/label/Label.vue Delegate props via reactiveOmit
src/app/components/scn/form/useFormField.ts Remove vee-validate field context helper
src/app/components/scn/form/injectionKeys.ts Remove legacy injection key
src/app/components/scn/form/index.ts Remove vee-validate form exports
src/app/components/scn/form/FormMessage.vue Remove vee-validate error message wrapper
src/app/components/scn/form/FormLabel.vue Remove legacy label wrapper
src/app/components/scn/form/FormItem.vue Remove legacy form item wrapper
src/app/components/scn/form/FormDescription.vue Remove legacy description wrapper
src/app/components/scn/form/FormControl.vue Remove legacy control wrapper
src/app/components/scn/field/index.ts New field component exports + variants
src/app/components/scn/field/FieldTitle.vue New field title component
src/app/components/scn/field/FieldSet.vue New fieldset layout component
src/app/components/scn/field/FieldSeparator.vue New separator-with-label layout
src/app/components/scn/field/FieldLegend.vue New legend/label variant component
src/app/components/scn/field/FieldLabel.vue New label wrapper for fields
src/app/components/scn/field/FieldGroup.vue New field group/container component
src/app/components/scn/field/FieldError.vue New error list/single message renderer
src/app/components/scn/field/FieldDescription.vue New description component
src/app/components/scn/field/FieldContent.vue New content wrapper component
src/app/components/scn/field/Field.vue New field wrapper using fieldVariants
src/app/components/preference/form/PreferenceFormSize.vue Migrate preference form to TanStack Form
src/app/components/guest/GuestList.vue Replace FormInputStateInfo with plain text
src/app/components/form/radio/FormRadioGroupItem.vue Remove form-specific radio item
src/app/components/form/input/state/FormInputStateWarning.vue Remove legacy Vuelidate warning state
src/app/components/form/input/state/FormInputStateSuccess.vue Remove legacy Vuelidate success state
src/app/components/form/input/state/FormInputStateInfo.vue Remove legacy Vuelidate info state
src/app/components/form/input/state/FormInputStateError.vue Remove legacy Vuelidate error state
src/app/components/form/input/state/FormInputState.vue Remove legacy input state wrapper
src/app/components/form/input/FormInputUsername.vue Remove legacy Vuelidate username input
src/app/components/form/input/FormInputUrl.vue Remove legacy URL input component
src/app/components/form/input/FormInputPhoneNumber.vue Remove legacy phone input component
src/app/components/form/input/FormInputPassword.vue Remove legacy password input component
src/app/components/form/input/FormInputEmailAddress.vue Remove legacy email input component
src/app/components/form/input/FormInputCaptcha.vue Remove legacy captcha input component
src/app/components/form/input/FormInput.vue Remove legacy generic input component
src/app/components/form/FormSupportReport.vue Refactor support report form to TanStack Form
src/app/components/form/FormSupportIssue.vue Refactor support issue form to TanStack Form
src/app/components/form/FormSupportIdea.vue Refactor support idea form to TanStack Form
src/app/components/form/FormSupportContact.vue Refactor support contact form to TanStack Form
src/app/components/form/FormSessionCreate.vue Refactor sign-in form to TanStack Form
src/app/components/form/FormGuest.vue Refactor guest selection form to TanStack Form
src/app/components/form/FormEvent.vue Refactor event create/edit form to TanStack Form
src/app/components/form/FormEarlyBird.vue Refactor early-bird form to TanStack Form
src/app/components/form/FormDelete.vue Refactor delete confirmation form to TanStack Form
src/app/components/form/FormContact.vue Refactor contact create/edit form to TanStack Form
src/app/components/form/field/FormFieldConsent.vue Remove legacy consent field wrapper
src/app/components/form/account/registration/FormAccountRegistrationAge.vue Refactor age step to TanStack Form
src/app/components/form/account/registration/FormAccountRegistration.vue Refactor registration form to TanStack Form
src/app/components/form/account/password/FormAccountPasswordResetRequest.vue Refactor reset request form to TanStack Form
src/app/components/form/account/password/FormAccountPasswordReset.vue Refactor reset form to TanStack Form
src/app/components/form/account/password/FormAccountPasswordChange.vue Refactor password change to TanStack Form
src/app/components/form/account/FormAccountLegalConsent.vue Refactor legal consent to TanStack Form
src/app/components/event/report/EventReportForm.vue Refactor report form to TanStack Form
src/app/components/app/radio/AppRadioGroup.vue Remove form-specific radio item branching
src/app/components/app/AppTipTap.vue Decouple TipTap from Vuelidate BaseValidation
src/app/components/app/AppForm.vue Remove legacy Vuelidate-based form wrapper
pnpm-lock.yaml Lock updates for dependency migration
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to +54
<form.Field
v-slot="{ field: slugField }"
name="slug"
:validators="{
onBlurAsync: async ({ value: val }) => {
if (!val) return undefined

const slugExists = await validateEventSlugFn(val)
return slugExists
? t('validationExistenceNone', { slug: val })
: undefined
},
}"
>
{{ t('isRemote') }}
</FormCheckbox>
</FormInput>
<!-- <FormInput
v-if="v$.isInPerson.$model"
id-label="input-location"
:placeholder="t('globalPlaceholderAddress').replace('\n', ' ')"
:title="t('location')"
type="text"
:value="v$.location"
@input="form.location = $event"
>
<template #stateError>
<FormInputStateError
:form-input="v$.location"
validation-property="lengthMax"
>
{{ t('globalValidationLength') }}
</FormInputStateError>
</template>
<template #stateInfo>
<FormInputStateInfo>
{{ t('stateInfoLocation') }}
</FormInputStateInfo>
</template>
</FormInput> -->
<FormInputUrl :form-input="v$.url" @input="form.url = $event" />
<FormInput
:title="t('description')"
type="tiptap"
:value="v$.description"
@input="form.description = $event"
>
<client-only v-if="v$.description">
<AppTipTap
:value="v$.description"
@input="form.description = $event"
<FieldError
v-if="isFieldInvalid(slugField)"
:errors="normalizeFieldErrors(slugField.state.meta.errors)"
/>
</client-only>
<template #stateError>
<FormInputStateError
:form-input="v$.description"
validation-property="lengthMax"
</form.Field>
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The slug field currently has no input/control (only a FieldError), and its uniqueness check is implemented as onBlurAsync. Since there’s nothing to blur, this validator will never run, and on submit only the Zod schema runs (so slug uniqueness is no longer validated). Consider either adding an (even hidden) input and validating onChange/onSubmitAsync, or moving the uniqueness check into a submit-time validator so it always runs before creating/updating events.

Copilot uses AI. Check for mistakes.
@dargmuesli dargmuesli force-pushed the feat/components/form-refactor branch from 383e338 to ad8e6a6 Compare April 7, 2026 13:49
@dargmuesli dargmuesli linked an issue Apr 7, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(form): debounce api validations refactor(components): migrate forms from vuelidate and vee-validate to tanstack/form

2 participants