Skip to content

feat(Form): add HTML5 validation to programmatic submit#6002

Merged
benjamincanac merged 4 commits intonuxt:v4from
IlyaSemenov:feat/form-html5-validation
Feb 15, 2026
Merged

feat(Form): add HTML5 validation to programmatic submit#6002
benjamincanac merged 4 commits intonuxt:v4from
IlyaSemenov:feat/form-html5-validation

Conversation

@IlyaSemenov
Copy link
Contributor

@IlyaSemenov IlyaSemenov commented Feb 7, 2026

🔗 Linked issue

Resolves #5139

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

When calling form.submit() programmatically, the form now triggers native HTML5 validation (required, type, min, max, etc.) before submission. If validation fails, submission is prevented and browser validation messages are displayed.

This is particularly useful when the submit button is outside the form element, such as in modal footers.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

💡 Notes

This adds an inner template ref within the UForm component. I could instead use document.getElementById(), but then the tests wouldn't work because the fixture mount helper doesn't actually insert the mounted component into document.body. I could rework the helper to address that, but the template ref approach seemed cleaner as it introduced less coupling overall.

EDIT: As suggested by coderabbit, this also fixes nearby documentation formatting errors (not directly affected by the PR).

@github-actions github-actions bot added the v4 #4488 label Feb 7, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds HTML5 validation to programmatic form submission: Form.vue now captures the root element via a formEl ref and calls reportValidity() to abort submission when native validation fails (the check exists in both the internal submit and the exposed api.submit). Tests and a new fixture cover programmatic submit behavior for invalid/valid forms and nested-form behavior (nested forms render as div and do not trigger HTML5 validation). Documentation was updated with an HTML5 validation section and the public API docs were expanded to document dirtyFields, touchedFields, and blurredFields. Runtime public method signatures were not changed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding HTML5 validation support to the programmatic form.submit() method.
Linked Issues check ✅ Passed The PR fulfills all coding requirements from issue #5139: programmatic submit() now triggers HTML5 validation before submission, prevents submission on validation failure, and enables the modal footer submit button pattern.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing HTML5 validation in form.submit(): documentation updates, Form.vue implementation, and test coverage with fixtures.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into v4
Description check ✅ Passed The pull request description clearly describes the changeset: it documents that programmatic form.submit() now triggers HTML5 validation, when this is useful, and how it was implemented, matching the changes across documentation, implementation, and tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/content/docs/2.components/form.md (1)

229-230: ⚠️ Potential issue | 🟡 Minor

Pre-existing typo: missing | (pipe) operator in type signatures.

Lines 229 and 230 are missing the union pipe between keyof T and RegExp:

  • Line 229: getErrors(path?: keyof T RegExp) → should be keyof T | RegExp
  • Line 230: setErrors(errors: FormError[], name?: keyof T RegExp) → should be keyof T | RegExp

Not introduced by this PR, but worth fixing while you're editing this table.

🧹 Nitpick comments (2)
src/runtime/components/Form.vue (1)

105-105: Consider narrowing the ref type to reflect the actual runtime possibilities.

formEl is typed as ref<HTMLFormElement>() but it can also hold an HTMLDivElement when the form is nested. The instanceof guard at Line 403 handles this at runtime, but the type is slightly misleading.

Suggested type change
-const formEl = ref<HTMLFormElement>()
+const formEl = ref<HTMLFormElement | HTMLDivElement>()
test/components/Form.spec.ts (1)

645-667: Consider using afterEach for spy cleanup to prevent leaks on test failure.

If a test throws before reaching mockRestore(), the spy leaks to subsequent tests. A safer pattern:

Suggested pattern
   describe('HTML5 validation', () => {
+    let reportValiditySpy: ReturnType<typeof vi.spyOn> | undefined
+
+    afterEach(() => {
+      reportValiditySpy?.mockRestore()
+    })
+
     it('programmatic submit() triggers HTML5 validation and prevents submission when invalid', async () => {
       const onSubmit = vi.fn()
-      const reportValiditySpy = vi.spyOn(HTMLFormElement.prototype, 'reportValidity').mockReturnValue(false)
+      reportValiditySpy = vi.spyOn(HTMLFormElement.prototype, 'reportValidity').mockReturnValue(false)
       // ... rest of test without mockRestore at the end

@IlyaSemenov IlyaSemenov force-pushed the feat/form-html5-validation branch from d0cdb27 to e2d3435 Compare February 7, 2026 06:44
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 7, 2026

npm i https://pkg.pr.new/@nuxt/ui@6002

commit: ad58c53

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/components/Form.spec.ts`:
- Around line 647-650: The test file uses afterEach but it's not imported from
vitest causing a build error; update the vitest import (the import statement
that currently brings in functions like describe/it/etc.) to also include
afterEach so the afterEach(() => { reportValiditySpy?.mockRestore();
reportValiditySpy = undefined }) call resolves, and ensure the symbol name is
spelled exactly as afterEach to match the test hook.

@IlyaSemenov
Copy link
Contributor Author

I'm not sure why the tests failed in CI in an unrelated file. They work fine on my mac.

That said, I am not 100% sure that vi.spyOn(HTMLFormElement.prototype, 'reportValidity') is a correct approach, chances are that this leaks somehow and there is a race condition that breaks the tests randomly. Can anyone please take a look?

@benjamincanac benjamincanac requested a review from romhml February 9, 2026 09:02
@romhml
Copy link
Member

romhml commented Feb 11, 2026

@IlyaSemenov tests should be good, the error didn't seem related to your changes at all. Maybe we could use the @invalid native input event on the fixture to avoid spying on the prototype?

@IlyaSemenov IlyaSemenov force-pushed the feat/form-html5-validation branch from cb735a5 to 5208f79 Compare February 13, 2026 11:11
@IlyaSemenov
Copy link
Contributor Author

@romhml Good idea, I replaced the reportValidity prototype spy with onInvalid handler. The tests are much simpler now. 👍

@benjamincanac benjamincanac merged commit ed552fc into nuxt:v4 Feb 15, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

form.submit() to perform HTML5 validation

3 participants