Skip to content

Commit ed3773d

Browse files
committed
2 parents 9c2eb00 + 703d8a0 commit ed3773d

18 files changed

Lines changed: 821 additions & 559 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ yarn.lock
88
.vercel
99
.output
1010
.vinxi
11+
.netlify
1112

1213
/build/
1314
/api/
1415
/server/build
1516
/public/build
16-
.vinxi
17+
1718
# Sentry Config File
1819
.env.sentry-build-plugin
1920
dist
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
---
2+
title: Announcing TanStack Form v1
3+
published: 03/03/2025
4+
authors:
5+
- Corbin Crutchley
6+
---
7+
8+
![TanStack Form v1](/blog-assets/announcing-tanstack-form-v1/form_header.png)
9+
10+
We're excited to announce the first stable version of [TanStack Form](/form/v1) is live and ready for usage in production! 🥳
11+
12+
We support five frameworks at launch: React, Vue, Angular, Solid, and Lit, as well as a myriad of features for each specific framework.
13+
14+
# How to install
15+
16+
```shell
17+
$ npm i @tanstack/react-form
18+
# or
19+
$ npm i @tanstack/vue-form
20+
# or
21+
$ npm i @tanstack/angular-form
22+
# or
23+
$ npm i @tanstack/solid-form
24+
# or
25+
$ npm i @tanstack/lit-form
26+
```
27+
28+
# A bit of history
29+
30+
It was nearly two years ago when [I saw Tanner's BlueSky (an invite-only platform at the time) post announcing that he was working on a new project: TanStack Form](https://bsky.app/profile/tannerlinsley.com/post/3ju5z473w5525).
31+
32+
![A back and forth between Tanner and myself on Bluesky about TanStack Form](/blog-assets/announcing-tanstack-form-v1/tanstack_form_bluesky_announce.png)
33+
34+
At the time, I had just launched an alternative form library for React called "[HouseForm](https://web.archive.org/web/20240101000000*/houseform.dev)" and I was immediately enamored by some of the ideas Tanner's library brought to the table.
35+
36+
I was fortunate enough to attend a hackathon that Tanner was also going to soon after and we were able to get some time to work on integrating some APIs from HouseForm into the project.
37+
38+
Since that time, Tanner's handed much of the reigns of Form over to me and a wonderful group of additional maintainers.
39+
40+
So, what have we built in that time?
41+
42+
# Features
43+
44+
One of the advantages of being in the oven for so long is that TanStack Form launches with a flurry of features you can leverage day one.
45+
46+
Let's go over _just a few_ of them using React's adapter as examples.
47+
48+
## Extreme type safety
49+
50+
Like many all of the TanStack projects, Form has revolutionized what it means to be a "type-safe" form library.
51+
52+
```tsx
53+
const form = useForm({
54+
defaultValues: {
55+
name: "",
56+
age: 0
57+
}
58+
});
59+
60+
// TypeScript will correctly tell you that `firstName` is not a valid field
61+
<form.Field name="firstName"/>
62+
63+
// TypeScript will correctly tell you that `name`'s type is a `string`, not a `number`
64+
<form.Field name="name" children={field => <NumberInput value={field.state.value}/>}/>
65+
```
66+
67+
We even support type-checking what errors are returned in `<form.Field>`:
68+
69+
```tsx
70+
<form.Field
71+
name="age"
72+
validators={{
73+
onChange: ({ value }) => (value < 12 ? { tooYoung: true } : undefined),
74+
}}
75+
children={(field) => (
76+
<>
77+
<NumberInput value={field.state.value} />
78+
// TypeScript will correctly tell you that `errorMap.onChange` // is an object,
79+
not a string
80+
<p>{field.state.meta.errorMap.onChange}</p>
81+
</>
82+
)}
83+
/>
84+
```
85+
86+
> Oh, yeah, we support field-based validation as well as form validation. Mix-n-match them!
87+
88+
The best part? [You won't need to pass any typescript generics to get this level of type safety](/form/latest/docs/philosophy#generics-are-grim). Everything is inferred from your runtime usage.
89+
90+
## Schema validation
91+
92+
Thanks to the awesome work by the creators of [Zod](http://zod.dev/), [Valibot](https://valibot.dev), and [ArkType](https://arktype.io/), we support [Standard Schema](https://github.com/standard-schema/standard-schema) out of the box; no other packages needed.
93+
94+
```tsx
95+
const userSchema = z.object({
96+
age: z.number().gte(13, 'You must be 13 to make an account'),
97+
})
98+
99+
function App() {
100+
const form = useForm({
101+
defaultValues: {
102+
age: 0,
103+
},
104+
validators: {
105+
onChange: userSchema,
106+
},
107+
})
108+
return (
109+
<div>
110+
<form.Field
111+
name="age"
112+
children={(field) => {
113+
return <>{/* ... */}</>
114+
}}
115+
/>
116+
</div>
117+
)
118+
}
119+
```
120+
121+
## Async validation
122+
123+
That's not all, though! We also support async functions to validate your code; complete with built-in debouncing and [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)-based cancellation:
124+
125+
```tsx
126+
<form.Field
127+
name="age"
128+
asyncDebounceMs={500}
129+
validators={{
130+
onBlurAsync: async ({ value, signal }) => {
131+
const currentAge = await fetchCurrentAgeOnProfile({ signal })
132+
return value < currentAge ? 'You can only increase the age' : undefined
133+
},
134+
}}
135+
/>
136+
```
137+
138+
## Platform support
139+
140+
Not only do we support multiple frameworks as we mentioned from the start; we support multiple runtimes. Whether you're using React Native, NativeScript, or even SSR solutions like Next.js or [TanStack Start](/start), we have you covered.
141+
142+
In fact, if you're using SSR solutions, we even make server-side form validation a breeze:
143+
144+
```typescript
145+
// app/routes/index.tsx, but can be extracted to any other path
146+
import { createServerValidate, getFormData } from '@tanstack/react-form/start'
147+
import { yourSchemaHere } from '~/constants/forms'
148+
149+
const serverValidate = createServerValidate({
150+
...formOpts,
151+
onServerValidate: yourSchemaHere,
152+
})
153+
154+
export const getFormDataFromServer = createServerFn({ method: 'GET' }).handler(
155+
async () => {
156+
return getFormData()
157+
}
158+
)
159+
```
160+
161+
> This code sample excludes some of the relevant code to keep things glanceable. [For more details on our SSR integration, please check our docs.](/form/latest/docs/framework/react/guides/ssr)
162+
163+
And boom, the exact same validation logic is running on both your frontend and backend. Your forms will even show errors when JavaScript is disabled on the user's browser!
164+
165+
# A look forward
166+
167+
We're not resting on our laurels, however - we have plans to add new features to v1 now that we're stable. These features include:
168+
169+
- [Persistence APIs](https://github.com/TanStack/form/pull/561)
170+
- [A Svelte 5 adapter](https://github.com/TanStack/form/issues/516)
171+
- [Better DX for transforming values on submission](https://github.com/TanStack/form/issues/418)
172+
- [Form Groups](https://github.com/TanStack/form/issues/419)
173+
174+
And much more.
175+
176+
# Thank **you**
177+
178+
There's so many people I'd like to thank that once I'd gotten started I'd never end. Instead, I'll address each group of folks I want to thank.
179+
180+
- Thank you to our contributors: So many people had to come together to make this happen. From maintainers of other TanStack projects giving us guidance, to drive-by PRs; it all helped us get across the line.
181+
182+
- Thank you to our early adopters: The ones who took a risk on us and provided invaluable feedback on our APIs and functionality.
183+
- Thank you to the content creators who covered our tools: You brought more eyes to our project - making it better through education and feedback.
184+
- Thank you to the broader community: Your excitement to use our tools have driven the team immensely.
185+
186+
And finally, thank **you** for taking the time to read and explore our newest tool. ❤️

app/components/Doc.tsx

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { DocTitle } from '~/components/DocTitle'
77
import { Markdown } from '~/components/Markdown'
88
import { Toc } from './Toc'
99
import { twMerge } from 'tailwind-merge'
10+
import { TocMobile } from './TocMobile'
1011

1112
type DocProps = {
1213
title: string
@@ -89,55 +90,59 @@ export function Doc({
8990
}, [])
9091

9192
return (
92-
<div
93-
className={twMerge(
94-
'w-full flex bg-white/70 dark:bg-black/40 mx-auto rounded-xl max-w-[936px]',
95-
isTocVisible && 'max-w-full'
96-
)}
97-
>
93+
<React.Fragment>
94+
{shouldRenderToc ? <TocMobile headings={headings} /> : null}
9895
<div
9996
className={twMerge(
100-
'flex overflow-auto flex-col w-full p-4 lg:p-6',
101-
isTocVisible && '!pr-0'
97+
'w-full flex bg-white/70 dark:bg-black/40 mx-auto rounded-xl max-w-[936px]',
98+
isTocVisible && 'max-w-full',
99+
shouldRenderToc && 'lg:pt-0'
102100
)}
103101
>
104-
{title ? <DocTitle>{title}</DocTitle> : null}
105-
<div className="h-4" />
106-
<div className="h-px bg-gray-500 opacity-20" />
107-
<div className="h-4" />
108102
<div
109-
ref={markdownContainerRef}
110103
className={twMerge(
111-
'prose prose-gray prose-sm prose-p:leading-7 dark:prose-invert max-w-none',
112-
isTocVisible && 'pr-4 lg:pr-6',
113-
'styled-markdown-content'
104+
'flex overflow-auto flex-col w-full p-4 lg:p-6',
105+
isTocVisible && '!pr-0'
114106
)}
115107
>
116-
<Markdown htmlMarkup={markup} />
117-
</div>
118-
<div className="h-12" />
119-
<div className="w-full h-px bg-gray-500 opacity-30" />
120-
<div className="py-4 opacity-70">
121-
<a
122-
href={`https://github.com/${repo}/tree/${branch}/${filePath}`}
123-
className="flex items-center gap-2"
108+
{title ? <DocTitle>{title}</DocTitle> : null}
109+
<div className="h-4" />
110+
<div className="h-px bg-gray-500 opacity-20" />
111+
<div className="h-4" />
112+
<div
113+
ref={markdownContainerRef}
114+
className={twMerge(
115+
'prose prose-gray prose-sm prose-p:leading-7 dark:prose-invert max-w-none',
116+
isTocVisible && 'pr-4 lg:pr-6',
117+
'styled-markdown-content'
118+
)}
124119
>
125-
<FaEdit /> Edit on GitHub
126-
</a>
120+
<Markdown htmlMarkup={markup} />
121+
</div>
122+
<div className="h-12" />
123+
<div className="w-full h-px bg-gray-500 opacity-30" />
124+
<div className="py-4 opacity-70">
125+
<a
126+
href={`https://github.com/${repo}/tree/${branch}/${filePath}`}
127+
className="flex items-center gap-2"
128+
>
129+
<FaEdit /> Edit on GitHub
130+
</a>
131+
</div>
132+
<div className="h-24" />
127133
</div>
128-
<div className="h-24" />
129-
</div>
130134

131-
{isTocVisible && (
132-
<div className="border-l border-gray-500/20 max-w-52 w-full hidden 2xl:block transition-all">
133-
<Toc
134-
headings={headings}
135-
activeHeadings={activeHeadings}
136-
colorFrom={colorFrom}
137-
colorTo={colorTo}
138-
/>
139-
</div>
140-
)}
141-
</div>
135+
{isTocVisible && (
136+
<div className="border-l border-gray-500/20 max-w-52 w-full hidden 2xl:block transition-all">
137+
<Toc
138+
headings={headings}
139+
activeHeadings={activeHeadings}
140+
colorFrom={colorFrom}
141+
colorTo={colorTo}
142+
/>
143+
</div>
144+
)}
145+
</div>
146+
</React.Fragment>
142147
)
143148
}

app/components/DocContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function DocContainer({
99
<div
1010
{...props}
1111
className={twMerge(
12-
'w-full max-w-full space-y-2 md:space-y-6 lg:space-y-8 p-2 md:p-6 lg:p-8',
12+
'w-full max-w-full p-2 md:p-6 lg:p-8',
1313
props.className
1414
)}
1515
>

0 commit comments

Comments
 (0)