🧘♂️ Simple form
The simple way to validate forms in your fullstack app.
---import { z } from "zod";import { createForm } from "simple:form";
const checkout = createForm({ quantity: z.number(), email: z.string().email(), allowAlerts: z.boolean(),});
const result = await Astro.locals.form.getData(checkout);
if (result?.data) { await myDb.insert(result.data); // proceed to checkout}---
<form method="POST"> <label for="quantity">Quantity</label> <input id="quantity" {...checkout.inputProps.quantity} />
<label for="email">Email</label> <input id="email" {...checkout.inputProps.email} />
<label for="allowAlerts">Allow alerts</label> <input id="allowAlerts" {...checkout.inputProps.allowAlerts} /></form>
Installation
Simple form is an Astro integration. You can install and configure this via the Astro CLI using astro add
:
npm run astro add simple-stack-form
After installing, you’ll need to add a type definition to your environment for editor hints. Add this reference to a new or existing src/env.d.ts
file:
/// <reference types="simple-stack-form/types" />
Simple form can be used with any framework. You can install it via npm:
# npmnpm i simple-stack-form
# pnpmpnpm i simple-stack-form
Create a validated form
Type: createForm(ZodRawShape): { inputProps: Record<string, InputProps>, validator: ZodRawShape }
You can create a simple form with the createForm()
function. This lets you specify a validation schema using Zod, where each input corresponds to an object key. Simple form supports string, number, or boolean (checkbox) fields.
import { createForm } from "simple:form";import z from "zod";
const signupForm = createForm({ name: z.string(), age: z.number().min(18).optional(), newsletterOptIn: z.boolean(),});
createForm()
returns both a validator and the inputProps
object. inputProps
converts each key of your validator to matching HTML props / attributes. The following props are generated today:
name
- the object key.type
-checkbox
for booleans,number
for numbers, andtext
for strings.aria-required
-true
by default,false
when.optional()
is used. Notearia-required
is used to add semantic meaning for screenreaders, but leave room to add a custom error banner.
Our signupForm
example generates the following inputProps
object:
const signupForm = createForm({ name: z.string(), age: z.number().min(18).optional(), newsletterOptIn: z.boolean(),});
signupForm.inputProps;/* name: { name: 'name', type: 'text', 'aria-required': true } age: { name: 'age', type: 'number', 'aria-required': false } newsletterOptIn: { name: 'newsletterOptIn', type: 'checkbox', 'aria-required': true }*/
Handle array values
You may want to submit multiple form values under the same name. This is common for multi-select file inputs, or generated inputs like “add a second contact.”
You can aggregate values under the same name using z.array()
in your validator:
import { createForm } from "simple:form";import z from "zod";
const contact = createForm({ contactNames: z.array(z.string()),});
Now, all inputs with the name contactNames
will be aggregated. This uses FormData.getAll()
behind the scenes:
---import { createForm } from "simple:form";import z from "zod";
const contact = createForm({ contactNames: z.array(z.string()),});
const res = await Astro.locals.form.getData(contact);console.log(res?.data);// contactNames: ["Ben", "George"]---
<form method="POST"> <label for="contact-1">Contact 1</label> <input id="contact-1" {...contact.inputProps.contactNames} /> {res.fieldErrors?.contactNames?.[0]} <label for="contact-2">Contact 2</label> <input id="contact-2" {...contact.inputProps.contactNames} /> {res.fieldErrors?.contactNames?.[1]}</form>
Note that fieldErrors
can be retrieved by index. For example, to get parse errors for the second input, use fieldErrors.contactNames[1]
.
Sanitize User Input
You may need to sanitize user input with rich text content. This is important for any text rendered as HTML to prevent Cross-Site Scripting (XSS) attacks. You can use the sanitize-html library for this:
npm install --save sanitize-htmlnpm install --save-dev @types/sanitize-html
Next, call sanitize-html
from your text validator with a Zod transform()
:
import sanitizeHtml from "sanitize-html";
const signupForm = createForm({ name: z.string(), name: z.string().transform((dirty) => sanitizeHtml(dirty)), age: z.number().min(18).optional(), newsletterOptIn: z.boolean(),});
You can find a sanitization example in our Astro playground