Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
9edad01
feat: initial work on FormGroupApi in core
crutchcorn Apr 16, 2026
dbc7ff6
chore: initial (and wrong) per-form-group-validation
crutchcorn Apr 16, 2026
1a22f52
chore: create a common FieldLikeAPI to adopt in form groups shortly
crutchcorn Apr 16, 2026
4775996
chore: implement FieldLike API on FormGroup
crutchcorn Apr 16, 2026
756ddf8
chore: revert changes to FormApi validation logic
crutchcorn Apr 16, 2026
c553439
chore: fix type issues with validation kind
crutchcorn Apr 16, 2026
d2eaa29
chore: minor fixes
crutchcorn Apr 16, 2026
ac22e7b
Revert "chore: revert changes to FormApi validation logic"
crutchcorn Apr 16, 2026
c2d061b
chore: filter fields to validate in formgroup
crutchcorn Apr 16, 2026
433ad6e
chore: improve store
crutchcorn Apr 16, 2026
22e49a7
chore: fix tests
crutchcorn Apr 16, 2026
db88246
chore: fix another test
crutchcorn Apr 16, 2026
4eb9b0f
chore: add submitmeta test
crutchcorn Apr 16, 2026
61d36cd
ci: apply automated fixes and generate docs
autofix-ci[bot] Apr 16, 2026
9b8bcb5
chore: add FormLike methods to FormGroup
crutchcorn Apr 20, 2026
86d9000
Merge branch 'form-group' of https://github.com/TanStack/form into fo…
crutchcorn Apr 20, 2026
d990bc6
chore: add type tests
crutchcorn Apr 22, 2026
66c5db9
chore: fix build
crutchcorn Apr 22, 2026
3c8ff5c
chore: fix type tests
crutchcorn Apr 22, 2026
4e4b272
ci: apply automated fixes and generate docs
autofix-ci[bot] Apr 22, 2026
ce63f8f
Merge branch 'main' into form-group
crutchcorn May 6, 2026
6094b9c
chore: export two missing items
crutchcorn May 6, 2026
1f96984
chore: add new interop FormGroupOptions type
crutchcorn May 6, 2026
ee121e4
chore: initial implement of useFormGroup in React
crutchcorn May 6, 2026
c46e342
ci: apply automated fixes and generate docs
autofix-ci[bot] May 6, 2026
3b756ae
chore: add formGroup to useForm
crutchcorn May 6, 2026
93ec42f
chore: add initial React implementation tests
crutchcorn May 6, 2026
da467f3
ci: apply automated fixes and generate docs
autofix-ci[bot] May 6, 2026
d1471f6
chore: add type tests for React adapter
crutchcorn May 6, 2026
e3e81b9
ci: apply automated fixes and generate docs
autofix-ci[bot] May 6, 2026
23e8066
chore: fix eslint
crutchcorn May 6, 2026
c86b424
ci: apply automated fixes and generate docs
autofix-ci[bot] May 6, 2026
4b81818
chore: add multi-step wizard
crutchcorn May 6, 2026
40b2758
ci: apply automated fixes and generate docs
autofix-ci[bot] May 6, 2026
e9ee38c
fix: handle resubmissions
crutchcorn May 6, 2026
bfa9464
fix: validate on resubmit
crutchcorn May 6, 2026
a86f781
feat: add better onDynamic handling to FormGroupApi
crutchcorn May 6, 2026
fec4e73
chore: first attempt to fix onDynamic change handling on groups
crutchcorn May 6, 2026
c3d6176
chore: attempt 2
crutchcorn May 6, 2026
973fab7
chore: attempt 3
crutchcorn May 6, 2026
7546f08
chore: attempt 4
crutchcorn May 6, 2026
c8ec12e
Revert "chore: attempt 4"
crutchcorn May 6, 2026
d499d20
chore: finalize fixing form errors
crutchcorn May 6, 2026
58ed8a2
chore: fix eslint
crutchcorn May 6, 2026
f69771b
chore: regenerate lockfile
crutchcorn May 6, 2026
7d9ca0d
ci: apply automated fixes and generate docs
autofix-ci[bot] May 6, 2026
9e78281
docs: update the wizard example
crutchcorn May 6, 2026
4837dd9
ci: apply automated fixes and generate docs
autofix-ci[bot] May 6, 2026
b189273
chore: initial work on Vue FormGroup API
crutchcorn May 7, 2026
1ee623c
ci: apply automated fixes and generate docs
autofix-ci[bot] May 7, 2026
1061988
Merge branch 'main' into form-group
crutchcorn May 8, 2026
e333776
chore: revert useContext changes
crutchcorn May 8, 2026
ad5bdd7
Merge branch 'main' into form-group
crutchcorn May 8, 2026
9371cfb
feat: add preact form group
crutchcorn May 8, 2026
d9f8097
chore: fix tests and export names
crutchcorn May 8, 2026
fa7ed5e
chore: add Preact wizard demo
crutchcorn May 8, 2026
0fbc378
feat: add FormGroup support for Solid adapter
crutchcorn May 8, 2026
caf608e
chore: add formgroup tests for Solid
crutchcorn May 8, 2026
30f20f2
chore: add type tests for Solid
crutchcorn May 8, 2026
d14c6c7
ci: apply automated fixes and generate docs
autofix-ci[bot] May 8, 2026
5458c9e
chore: add Solid group example
crutchcorn May 8, 2026
dcaebbe
ci: apply automated fixes and generate docs
autofix-ci[bot] May 8, 2026
a9c2e92
chore: fix test
crutchcorn May 8, 2026
71fb870
Merge branch 'main' into form-group
crutchcorn May 9, 2026
c5c3798
Merge branch 'main' into form-group
crutchcorn May 9, 2026
5c6a7e4
feat: add FormGroup impl to Svelte
crutchcorn May 9, 2026
ab4ab8b
ci: apply automated fixes and generate docs
autofix-ci[bot] May 9, 2026
85a036a
chore: add formGroup tests for Svelte
crutchcorn May 9, 2026
ec039ad
ci: apply automated fixes and generate docs
autofix-ci[bot] May 9, 2026
9e085a8
chore: add Svelte example
crutchcorn May 9, 2026
5bf025e
chore: fix sherif
crutchcorn May 9, 2026
3f386d9
feat: add Lit FormGroup API
crutchcorn May 9, 2026
2ff22da
chore: add Lit FormGroup tests
crutchcorn May 9, 2026
d711535
chore: refactor Lit tests
crutchcorn May 9, 2026
a8d754f
ci: apply automated fixes and generate docs
autofix-ci[bot] May 9, 2026
75b8306
feat: implement FormGroup in Angular
crutchcorn May 9, 2026
47a8664
chore: add FormGroup Angular spec
crutchcorn May 9, 2026
3a18322
ci: apply automated fixes and generate docs
autofix-ci[bot] May 9, 2026
53412be
feat: add withForm feature to Angular
crutchcorn May 9, 2026
02ca57e
chore: update Angular large-form example to use new `withForm` API
crutchcorn May 9, 2026
8cc45ec
docs: add docs to Angular withForm example
crutchcorn May 9, 2026
3a6f632
chore: add changeset
crutchcorn May 9, 2026
6edf9d4
chore: fix knip
crutchcorn May 9, 2026
596c8d5
chore: initial multi-step-wizard Angular example
crutchcorn May 9, 2026
001068c
Merge branch 'angular-with-form' into form-group
crutchcorn May 9, 2026
31c616a
chore: update the multi-step-wizard to use new API
crutchcorn May 9, 2026
4bc606c
Merge branch 'main' into form-group
crutchcorn May 9, 2026
771ff94
Merge branch 'main' into form-group
crutchcorn May 10, 2026
36f775c
Merge branch 'main' into form-group
crutchcorn May 10, 2026
db61223
chore: add multi-step-wizard example to Lit
crutchcorn May 10, 2026
9f68cbd
ci: apply automated fixes and generate docs
autofix-ci[bot] May 10, 2026
87d61f5
chore: add Vue example
crutchcorn May 10, 2026
c05d2ac
chore: fix CI
crutchcorn May 10, 2026
5d7440a
Merge branch 'main' into form-group
crutchcorn May 10, 2026
b16487a
chore: update to use Testing Library
crutchcorn May 10, 2026
d9811ec
chore: make tests more Testing Library-y
crutchcorn May 10, 2026
17d0e6c
Merge branch 'main' into form-group
crutchcorn May 11, 2026
d9a5697
fix: isSubmitting now works with FormGroup
crutchcorn May 11, 2026
df0276a
chore: fix Lit warning
crutchcorn May 11, 2026
01562d8
ci: apply automated fixes and generate docs
autofix-ci[bot] May 11, 2026
58ec143
chore: add currently failing tests
crutchcorn May 11, 2026
9b572df
fix: handle validation better in form groups
crutchcorn May 11, 2026
ddf1756
chore: add test for duped group schema
crutchcorn May 11, 2026
b0cfe0e
chore: update `form` error field to `group`
crutchcorn May 11, 2026
4293d47
chore: fix CI
crutchcorn May 11, 2026
ea881c4
ci: apply automated fixes and generate docs
autofix-ci[bot] May 11, 2026
d41293b
chore: add test for isGroupValid and isFieldsValid
crutchcorn May 11, 2026
c44ca86
ci: apply automated fixes and generate docs
autofix-ci[bot] May 11, 2026
0b6a2a7
chore: refactor FormGroup to core
crutchcorn May 11, 2026
dec13e0
chore: refactor Lit Form to use Lit Store properly
crutchcorn May 11, 2026
e6b9b8d
chore: fix eslint
crutchcorn May 11, 2026
f19703a
ci: apply automated fixes and generate docs
autofix-ci[bot] May 11, 2026
bbc707a
chore: remove temp changeset
crutchcorn May 11, 2026
083b28a
docs: initial docs for React adapter
crutchcorn May 11, 2026
f36d963
docs: add multi-step-wizard examples to docs site
crutchcorn May 11, 2026
a030e78
docs: add form group state docs for React
crutchcorn May 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/assets/stepper.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
"label": "Arrays",
"to": "framework/react/guides/arrays"
},
{
"label": "Form Groups",
"to": "framework/react/guides/form-groups"
},
{
"label": "Linked Fields",
"to": "framework/react/guides/linked-fields"
Expand Down Expand Up @@ -644,6 +648,10 @@
"label": "Simple",
"to": "framework/react/examples/simple"
},
{
"label": "Multi-Step Wizard",
"to": "framework/react/examples/multi-step-wizard"
},
{
"label": "Arrays",
"to": "framework/react/examples/array"
Expand Down Expand Up @@ -697,6 +705,10 @@
"label": "Simple",
"to": "framework/vue/examples/simple"
},
{
"label": "Multi-Step Wizard",
"to": "framework/vue/examples/multi-step-wizard"
},
{
"label": "Arrays",
"to": "framework/vue/examples/array"
Expand All @@ -714,6 +726,10 @@
"label": "Simple",
"to": "framework/angular/examples/simple"
},
{
"label": "Multi-Step Wizard",
"to": "framework/angular/examples/multi-step-wizard"
},
{
"label": "Arrays",
"to": "framework/angular/examples/array"
Expand All @@ -735,6 +751,10 @@
"label": "Simple",
"to": "framework/solid/examples/simple"
},
{
"label": "Multi-Step Wizard",
"to": "framework/solid/examples/multi-step-wizard"
},
{
"label": "Arrays",
"to": "framework/solid/examples/array"
Expand All @@ -756,6 +776,10 @@
"label": "Simple",
"to": "framework/lit/examples/simple"
},
{
"label": "Multi-Step Wizard",
"to": "framework/lit/examples/multi-step-wizard"
},
{
"label": "Array",
"to": "framework/lit/examples/array"
Expand All @@ -770,13 +794,30 @@
}
]
},
{
"label": "preact",
"children": [
{
"label": "Simple",
"to": "framework/preact/examples/simple"
},
{
"label": "Multi-Step Wizard",
"to": "framework/preact/examples/multi-step-wizard"
}
]
},
{
"label": "svelte",
"children": [
{
"label": "Simple",
"to": "framework/svelte/examples/simple"
},
{
"label": "Multi-Step Wizard",
"to": "framework/svelte/examples/multi-step-wizard"
},
{
"label": "Arrays",
"to": "framework/svelte/examples/array"
Expand Down
14 changes: 12 additions & 2 deletions docs/framework/angular/guides/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ To use an array, you can use `field.api.state.value` on an array value:
standalone: true,
imports: [TanStackField],
template: `
<ng-container [tanstackField]="form" name="people" mode="array" #people="field">
<ng-container
[tanstackField]="form"
name="people"
mode="array"
#people="field"
>
<div>
@for (_ of people.api.state.value; track $index) {
<!-- ... -->
Expand Down Expand Up @@ -101,7 +106,12 @@ export class AppComponent {
template: `
<form (submit)="handleSubmit($event)">
<div>
<ng-container [tanstackField]="form" name="people" mode="array" #people="field">
<ng-container
[tanstackField]="form"
name="people"
mode="array"
#people="field"
>
<div>
@for (_ of people.api.state.value; track $index) {
<ng-container
Expand Down
5 changes: 1 addition & 4 deletions docs/framework/angular/guides/form-composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,7 @@ Finally, in the parent component, create the form with `injectForm` and pass it
```angular-ts
// app.component.ts
import { Component } from '@angular/core'
import {
TanStackWithForm,
injectForm,
} from '@tanstack/angular-form'
import { TanStackWithForm, injectForm } from '@tanstack/angular-form'
import { ChildForm } from './child-form.component'
import { peopleFormOpts } from './shared-form'
Expand Down
177 changes: 177 additions & 0 deletions docs/framework/react/guides/form-groups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
---
id: form-groups
title: Form Groups
---

When building a multi-stage form that has many stages, like so:

![Form stepper](https://raw.githubusercontent.com/TanStack/form/main/docs/assets/stepper.png)

It's common for each step to have its own form. However, this complicates the form submission and validation process by requiring you to add complex logic.

Luckily, TanStack Form provides a way to build out sub-forms that make this kind of development trivial to implement: `<form.FormGroup>`.

## Usage

To use a form group in TanStack Form, you'll use `useForm` or [`useAppForm`](./form-composition.md) to create a `form` variable, then reference its `FormGroup` component like you would a `Field`:

```tsx
const form = useForm({
defaultValues: {
step1: {
name: ""
},
step2: {
age: 0
}
}
})

return (
<form.FormGroup
name="step1"
children={group => (
// `group` here has all of the form-like methods you'd expect like `deleteField` or `insertFieldValue`
// ...
)}
/>
)
```

This becomes much more useful when paired with external state to conditionally render a `FormGroup`:

```tsx
const [step, setStep] = useState(0)
const form = useForm({
defaultValues: {
step1: {
name: ""
},
step2: {
age: 0
}
}
})

return (
<>
{step === 0 ? <form.FormGroup
name="step1"
onGroupSubmit={() => {
// We can move the step forward when validation passes
setStep(step + 1)
}}
onGroupSubmitInvalid={() => {
// Or handle invalid submissions, just like a top-level form
}}
onSubmitMeta={{} as SomeType}
children={group => (
// Use `group.handleSubmit()` to submit the sub-form, but not the parent form
// ...
)}
/> : null }
{step === 1 ? <form.FormGroup
name="step2"
children={group => (
// Then, use `form.handleSubmit()` to submit the entire form
// ...
)}
/> : null }
</>
)
```

## Form Group Validation

Form groups have a distinct validation proceedure that we think makes sense for sub-forms:

- Form groups can have their own validation:

```tsx
<form.FormGroup
name="step1"
validators={{ onChange: () => 'Error' }}
children={(group) => {
group.state.meta.errorMap // {onChange: "Error" | undefined}
group.state.meta.errors // ("Error")[]
}}
/>
```

- Can set errors on sub-fields:

```tsx
<form.FormGroup
name="step1"
validators={{
onChange: ({ value, groupApi }) => ({
group: value.name === 'error' ? 'Group error' : undefined,
fields: {
// Must use the name of the field relative to the FormGroup as the error key,
// to stay consistent with how standard schema works with form groups
name: value.name === 'error' ? 'Field error' : undefined,
},
}),
}}
/>
```

- And can even accept standard schemas:

```tsx
<form.FormGroup
name="step1"
validators={{
onChange: z.object({
name: z.string().min(2),
}),
}}
/>
```

> The reason we don't use the full path names for fields is so that you can compose your schemas like so:
>
> ```
> const step1Schema = z.object({
> name: z.string().min(2)
> })
>
> const schema = z.object({
> step1: step1Schema,
> step2: step2Schema
> })
> ```
>
> And pass the `step1Schema` to a form group and `schema` to the parent form. That way, partially validated data will still flag errors if the group is bypassed.

### Dynamic Group Validation

If you want to use [dynamic validation (`onDynamic`)](./dynamic-validation.md) with a form group, do not rely on the `onDynamic` validator passed to `useForm`:

```tsx
useForm({
validationLogic: revalidateLogic(),
validators: {
// This validator will not run `onChange` when a sub-form is submitted;
// it will only run `onChange` when the form itself is submitted.
onDynamic: schema,
},
})
```

Instead, pass your sub-schema for the group to the `onDynamic` validation of the `FormGroup` itself:

```tsx
<form.FormGroup validators={{ onDynamic: step1Schema }} />
```

It will treat `group.submissionAttempts` as the way to change what validator is ran before/after submit.

## Form Group State

Just like you're able to access `group.state.meta.errors`, you're also able to access the group's value using `group.state.value`. Likewise, here are some valuable properties you can access in the `group.state.meta`:

- `group.state.meta.isFieldsValid`: `true` when the field-level validators have no errors
- `group.state.meta.isGroupValid`: `true` when the group-level validators have no errors
- `group.state.meta.isValid`: `true` when both the field-level and group-level validators have no errors
- `group.state.meta.isSubmitting`: `true` when the group is in the process of being submitted
16 changes: 16 additions & 0 deletions examples/angular/multi-step-wizard/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single

[*.md]
max_line_length = off
trim_trailing_whitespace = false
42 changes: 42 additions & 0 deletions examples/angular/multi-step-wizard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
/node_modules
npm-debug.log
yarn-error.log

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings

# System files
.DS_Store
Thumbs.db
Loading
Loading