Submit a service request
Grouped fields, draft saving, and final validation.
View MarkdownComplete the Forms setup first, or use the configuration, middleware, and layout files in the sidebar. Install @astro-utils/forms and @astrojs/node in an existing Astro project.
Try the interaction
Section titled “Try the interaction”Save an incomplete draft, then finish the required fields and submit. The preview confirms each action; it does not persist a draft after reload.
src/layouts/Layout.astro
---import { WebForms } from '@astro-utils/forms/forms.js';---<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <title><slot name="title" /></title> </head> <body> <WebForms> <slot /> </WebForms> </body></html>src/pages/application.astro
---import { BButton, BInput, BOption, BSelect, BTextarea, Bind, BindForm, FormErrors } from '@astro-utils/forms/forms.js';import Layout from '../layouts/Layout.astro';
type Form = { application: { legalName: string; birthDate: string | Date; service: string; details: string }};
const bind = Bind<Form>({application: Astro.locals.session.applicationDraft});let status = '';
function saveDraft() { Astro.locals.session.applicationDraft = bind.application; status = 'Draft saved.';}
async function submitApplication() { await sendData(bind.application); status = 'Application submitted.';
Astro.locals.session.applicationDraft = undefined; bind.application = {}; // clear the form}---<Layout> <span slot="title">Submit a service request</span>
<BindForm {bind}> <FormErrors title="Check your application" />
<fieldset> <legend>Applicant details</legend>
<label for="legalName">Legal name</label> <BInput id="legalName" name="application.legalName" autocomplete="name" maxlength={60} required />
<label for="birthDate">Date of birth</label> <BInput id="birthDate" name="application.birthDate" type="date" required /> </fieldset>
<fieldset> <legend>Request</legend>
<label for="service">Service</label> <BSelect id="service" name="application.service"> <BOption value="permit">Permit application</BOption> <BOption value="record">Public record request</BOption> </BSelect>
<label for="details">Supporting details</label> <BTextarea id="details" name="application.details" maxlength={600} required /> </fieldset>
<BButton onClick={saveDraft}>Save draft</BButton> <BButton onClick={submitApplication} whenFormOK>Submit application</BButton>
{status && <p role="status">{status}</p>} </BindForm></Layout>src/middleware.ts
import forms from '@astro-utils/forms';
export const onRequest = forms();astro.config.mjs
import { defineConfig } from 'astro/config';import node from '@astrojs/node';import forms from '@astro-utils/forms/dist/integration.js';
export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), integrations: [forms]});Preview Interactive demo
How it works
Section titled “How it works”In your project, Save draft writes a small object to the current session. Reloading the page restores it; submission clears it. This identifies the draft by browser session, not by an authenticated account. For a real service, authenticate the applicant and store drafts durably under their user ID. The demo creates no official request.
Continue
Section titled “Continue”Review binding and validation.