Skip to content

feat: add dirty() method to form fields, improve validation skipping logic - #16208

Merged
Rich-Harris merged 10 commits into
version-3from
touched-and-dirty
Jul 2, 2026
Merged

feat: add dirty() method to form fields, improve validation skipping logic#16208
Rich-Harris merged 10 commits into
version-3from
touched-and-dirty

Conversation

@Rich-Harris

Copy link
Copy Markdown
Member

Follow-up to #14692. This exposes a dirty() method on form fields, which is separate from touched() — it only becomes true once a value is actually edited (whether via user input or programmatically, after the form has mounted).

It also improves the logic around myform.validate() — rather than validating all fields that have been touched by default, it now only validates fields that are touched and dirty, meaning the user has edited a field and then blurred it (or it was set programmatically), which matches accessibility guidelines.

To validate every field regardless of dirty state, do myform.validate({ all: true }).

Draft because I guess we should add a test


Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

@changeset-bot

changeset-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6302071

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@svelte-docs-bot

Copy link
Copy Markdown

Comment thread packages/kit/src/exports/public.d.ts Outdated
Comment thread packages/kit/src/exports/public.d.ts
touched[key] = true;

if (element) {
// TODO only if the value actually changed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldn't this actually be implemented as part of this PR?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

@pkg-svelte-dev

pkg-svelte-dev Bot commented Jul 1, 2026

Copy link
Copy Markdown

Install the latest version of @sveltejs/kit from 6302071:

pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/6302071cee945fdca1630c15e3e175913a5f3af0

Open in pkg.svelte.dev: https://pkg.svelte.dev/@sveltejs/kit/c/6302071cee945fdca1630c15e3e175913a5f3af0

Comment thread packages/kit/src/runtime/client/remote-functions/form.svelte.js
@elliott-with-the-longest-name-on-github

Copy link
Copy Markdown
Contributor

/autofix

github-actions Bot and others added 3 commits July 1, 2026 22:40
… never treated as pristine after a reset and validation surfaces issues for all fields.

This commit fixes the issue reported at packages/kit/src/runtime/client/remote-functions/form.svelte.js:537

## Bug

In `packages/kit/src/runtime/client/remote-functions/form.svelte.js`, `handle_reset` (lines ~528-538) clears validation state but omits `submitted`:

```js
const handle_reset = async () => {
    await tick();
    input = convert_formdata(new FormData(form));
    raw_issues = [];
    touched = {};
    dirty = {};
    can_validate = {};
    // submitted was never reset
};
```

`submitted` is a `$state(false)` (line 114) set to `true` on submit (lines 427, 590) and never reset back anywhere. The `validate()` method gates the `can_validate` filter on it:

```js
if (!all && !submitted) {
    array = array.filter((issue) => can_validate[issue.name]);
}
```

Once `submitted` becomes `true`, this filter is permanently skipped.

## Failure mode

1.  User submits the form → `submitted = true`.
2.  User resets the form (native reset / reset event) → `handle_reset` clears `touched`/`dirty`/`can_validate` but leaves `submitted = true`.
3.  User edits a single field and `validate()` runs → because `submitted` is still `true`, the `can_validate` filter is skipped and validation issues for **all** fields (including untouched, un-blurred ones) are surfaced.

This contradicts the intended pristine-after-reset behavior ("all fields are always subject to validation... unless the form is reset, at which point it is treated as pristine").

## Fix

Add `submitted = false;` to `handle_reset` alongside the other resets, so a reset form returns to the pristine validation state.


Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Rich-Harris <hello@rich-harris.dev>
@Rich-Harris
Rich-Harris merged commit 223eaad into version-3 Jul 2, 2026
17 of 18 checks passed
@Rich-Harris
Rich-Harris deleted the touched-and-dirty branch July 2, 2026 00:07
Rich-Harris pushed a commit that referenced this pull request Jul 2, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to version-3, this PR
will be updated.

⚠️⚠️⚠️⚠️⚠️⚠️

`version-3` is currently in **pre mode** so this branch has prereleases
rather than normal releases. If you want to exit prereleases, run
`changeset pre exit` on `version-3`.

⚠️⚠️⚠️⚠️⚠️⚠️

# Releases
## @sveltejs/adapter-node@6.0.0-next.2

### Major Changes


- breaking: add `kit.paths.origin` config option, remove
`kit.prerender.origin` and the `adapter-node` `ORIGIN` environment
variable ([#16161](#16161))


### Patch Changes

- Updated dependencies
[[`3c434fb`](3c434fb),
[`a9284e8`](a9284e8),
[`3726a7a`](3726a7a),
[`f9d2240`](f9d2240),
[`a9284e8`](a9284e8),
[`7c040ba`](7c040ba),
[`223eaad`](223eaad),
[`223eaad`](223eaad),
[`3b907d4`](3b907d4),
[`fd628a5`](fd628a5),
[`223eaad`](223eaad),
[`6c1d035`](6c1d035),
[`178eac0`](178eac0),
[`c6562a9`](c6562a9),
[`8eca2ab`](8eca2ab),
[`61cf188`](61cf188)]:
  - @sveltejs/kit@3.0.0-next.6
## @sveltejs/kit@3.0.0-next.6

### Major Changes


- breaking: return no content for 204 responses
([#16200](#16200))


- breaking: form action responses now use the HTTP status code returned
from `fail` ([#16200](#16200))


- breaking: nested server-only directories
([#15685](#15685))


- breaking: add `kit.paths.origin` config option, remove
`kit.prerender.origin` and the `adapter-node` `ORIGIN` environment
variable ([#16161](#16161))


- breaking: don't abort navigation when calling `invalidate(All)` during
navigation ([#16188](#16188))


- breaking: allow `handleError` to influence status code
([#16162](#16162))


- breaking: forbid external redirects by default
([#16198](#16198))


### Minor Changes


- feat: use `type: 'module'` for service worker registrations
([#16169](#16169))


- feat: add `dirty()` property to form fields
([#16208](#16208))


- feat: add `cookies.parse` method
([#16203](#16203))


### Patch Changes


- fix: drain unconsumed request bodies so keep-alive connections don't
hang ([#16170](#16170))


- fix: properly handle Date objects in form.fields.set
([#16168](#16168))


- fix: skip clean fields when programmatically validating forms
([#16208](#16208))


- breaking: experimental remote form `validate({ includeUntouched })`
option is now `all`
([#16208](#16208))


- fix: return `undefined` from `fields.branch.issues()` when only
`fields.branch.leaf` has issues
([#16187](#16187))


- feat: add field.touched() helper to remote form fields
([#14692](#14692))
## @sveltejs/adapter-cloudflare@8.0.0-next.1

### Patch Changes


- fix: avoid overriding user's existing `_headers` rules
([#16183](#16183))

- Updated dependencies
[[`3c434fb`](3c434fb),
[`a9284e8`](a9284e8),
[`3726a7a`](3726a7a),
[`f9d2240`](f9d2240),
[`a9284e8`](a9284e8),
[`7c040ba`](7c040ba),
[`223eaad`](223eaad),
[`223eaad`](223eaad),
[`3b907d4`](3b907d4),
[`fd628a5`](fd628a5),
[`223eaad`](223eaad),
[`6c1d035`](6c1d035),
[`178eac0`](178eac0),
[`c6562a9`](c6562a9),
[`8eca2ab`](8eca2ab),
[`61cf188`](61cf188)]:
  - @sveltejs/kit@3.0.0-next.6

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
elliott-with-the-longest-name-on-github added a commit that referenced this pull request Aug 27, 2026
…ault only until edited (#16939)

Since #16331 moved coercion into `coerce_form_value`, `handle_input` has
stored the raw string of every typed value: `fields.value()` reported
`'20'` while typing and `200` after a reset, and an emptied number field
held `''` instead of `undefined`. Typed values are coerced again, the
same way the submitter path already is.

That re-exposes #15937: the default passed to `.as(type, default)` was
re-applied whenever the field's value became `undefined` or `null`, so
an emptied number input, or one cleared with `set()`, snapped back to
its default. The default now only applies until the field is dirty.
`defaultValue` and `defaultChecked` are unchanged, so reset still
restores it. `dirty` is the modified signal from #16208; `touched` would
drop the default on a tab-through. It is client-only, so SSR and a
failed no-JS submission still render defaults. #16237 keys the same rule
on key presence in the model instead. Fixes #15937.
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.

2 participants