TA

Handles form state management and Zod schema validation within Svelte 5 applications.

Install

mkdir -p .claude/skills/tanstack-form && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/1563" && unzip -o skill.zip -d .claude/skills/tanstack-form && rm skill.zip

Installs to .claude/skills/tanstack-form

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Use this skill when building or modifying forms with TanStack Form and Zod validation. Covers createForm, field-level validation, error handling, and mapping ProblemDetails API errors to form fields. Apply when adding new forms, implementing validation logic, or handling form submission in the Svelte frontend.
311 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Intermediate

Key capabilities

  • Manage form state using TanStack Form
  • Implement field-level validation with Zod schemas
  • Map API ProblemDetails errors to form fields
  • Handle form submission states and async operations
  • Integrate forms within Svelte components

How it works

It uses TanStack Form to manage state and Zod for validation, providing patterns to map backend error responses directly to UI fields.

Inputs & outputs

You give it
Form requirements and validation schema
You get back
Svelte component code with form logic and error handling

When to use tanstack-form

  • Creating complex forms
  • Validating user input with Zod
  • Managing form state in Svelte
  • Implementing form error handling

About this skill

TanStack Form

Documentation: tanstack.com/form. Use official docs when the local pattern is not enough.

Use TanStack Form (@tanstack/svelte-form) with Zod for form state management.

Zod Schema Generation

Schemas are generated from backend models and extended in feature slices:

// Generated in $generated/schemas.ts (auto-generated from backend)
export const LoginSchema = object({ email: email(), password: string() });
export type LoginFormData = Infer<typeof LoginSchema>;

// Extended in feature schemas.ts
// From src/lib/features/auth/schemas.ts
import { ChangePasswordModelSchema } from "$generated/schemas";

export const ChangePasswordSchema = ChangePasswordModelSchema.extend({
    confirm_password: string().min(6).max(100),
}).refine((data) => data.password === data.confirm_password, {
    message: "Passwords do not match",
    path: ["confirm_password"],
});
export type ChangePasswordFormData = Infer<typeof ChangePasswordSchema>;

// Re-export generated schemas
export { LoginSchema, type LoginFormData } from "$generated/schemas";

Basic Form Pattern

From src/Exceptionless.Web/ClientApp/src/routes/(auth)/login/+page.svelte:

<script lang="ts">
    import { createForm } from '@tanstack/svelte-form';
    import * as Field from '$comp/ui/field';
    import { Input } from '$comp/ui/input';
    import { Button } from '$comp/ui/button';
    import { type LoginFormData, LoginSchema } from '$features/auth/schemas';
    import { ariaInvalid, mapFieldErrors, problemDetailsToFormErrors } from '$shared/validation';

    const form = createForm(() => ({
        defaultValues: {
            email: '',
            password: ''
        } as LoginFormData,
        validators: {
            onSubmit: LoginSchema,
            onSubmitAsync: async ({ value }) => {
                const response = await login(value.email, value.password);
                if (response.ok) {
                    await goto('/');
                    return null;
                }
                return problemDetailsToFormErrors(response.problem);
            }
        }
    }));
</script>

<form onsubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
    <form.Field name="email">
        {#snippet children(field)}
            <Field.Field data-invalid={ariaInvalid(field)}>
                <Field.Label for={field.name}>Email</Field.Label>
                <Input
                    id={field.name}
                    type="email"
                    value={field.state.value}
                    onblur={field.handleBlur}
                    oninput={(e) => field.handleChange(e.currentTarget.value)}
                    aria-invalid={ariaInvalid(field)}
                />
                <Field.Error errors={mapFieldErrors(field.state.meta.errors)} />
            </Field.Field>
        {/snippet}
    </form.Field>

    <form.Subscribe selector={(state) => state.isSubmitting}>
        {#snippet children(isSubmitting)}
            <Button type="submit" disabled={isSubmitting}>
                {isSubmitting ? 'Logging in...' : 'Log In'}
            </Button>
        {/snippet}
    </form.Subscribe>
</form>

Server Error Handling

Convert ProblemDetails to form errors:

onSubmitAsync: async ({ value }) => {
    const response = await login(value.email, value.password);
    if (response.ok) return null;
    return problemDetailsToFormErrors(response.problem);
};

Form in Dialog Pattern

Close dialog only after successful submission:

let open = $state(false);

const form = createForm(() => ({
    defaultValues: { name: '' },
    validators: {
        onSubmit: mySchema,
        onSubmitAsync: async ({ value }) => {
            try {
                await createMutation.mutateAsync(value);
                open = false;
                return null;
            } catch (error: unknown) {
                if (error instanceof ProblemDetails) {
                    return problemDetailsToFormErrors(error);
                }
                return { form: 'An unexpected error occurred' };
            }
        }
    }
}));

References

See shadcn-svelte for Field component patterns.

When not to use it

  • When building forms outside of a Svelte 5 environment
  • When not using TanStack Form for state management

Prerequisites

TanStack FormZodSvelte 5

Limitations

  • Relies on specific project structures for schema generation
  • Requires integration with existing UI component libraries

How it compares

It provides a standardized pattern for mapping complex API error structures to form fields, which is typically handled manually.

Compared to similar skills

tanstack-form side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
tanstack-form (this skill)162moNo flagsIntermediate
svelte-ui-design239moNo flagsIntermediate
tanstack-query72moNo flagsIntermediate
svelte32moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry