Forms
Indexa is a fully static build with no adapter, so no form on it submits anywhere. This is stated in every form component, and it is worth being exact about what it means, because “static demo” covers a range.
Each form is real markup: real <form>, real named fields, real labels, real native validation attributes, real keyboard and screen-reader behaviour. What it lacks is a destination. Every one uses method="get" with an action pointing at its own route or the obvious next page, so submitting reloads a page with the values in the query string and nothing is lost, nothing errors, and nothing is sent.
<form method="get" action={form.action}>
That choice is deliberate. A POST to nowhere would 404 or 405; a GET to the same page degrades into a no-op that looks like a page refresh.
The six
/submit/ — the sell-your-car form. Five fields, a photo-upload slot and a tier choice, from submitData.
/value-my-car/ — the valuation form, with the four-step explainer band beside it and the shared “what happens next” rail.
/advertise/ — the slot-booking form: text fields plus two native selects, from advertiseData.
/sign-in/ — email and password, with the PasswordInput show/hide toggle and the four-segment PasswordStrength meter.
The home hero — a tabbed search panel; each tab’s form GETs to the route that tab is about, so “used cars” lands on /used-cars/.
AlertBand and WatchBand — the email-capture bands above the footer and on the saved dashboard. Both are single-field forms that GET to a real page.
The one that does something
/sign-in/ is the exception, and only just. Its script intercepts the submit, stores the email under indexa:account in localStorage, and navigates to /saved/, which reads it to personalize one line of copy. That is the entire account system: no password is checked, nothing is transmitted, and the page is noindex because a fake sign-in screen has no business in a search result.
It exists to make the favourites dashboard demonstrable — Saved Cars explains the store it hands off to.
Making one real
Three options, in increasing order of work.
A form service. Point action at a Formspree, Netlify Forms or Basin endpoint and change method to post. Zero code, stays fully static, and the field names are already sensible. The cost is a redirect to a third-party thank-you page unless you add their JavaScript.
A single on-demand route. Add an adapter, mark one page prerender = false, and handle the POST there with an Astro action. The rest of the site stays prerendered — the pattern is common in this theme family: one dynamic page, a static tree around it. Budget for validation, escaping, a spam gate and a delivery call, and keep the no-JS path working by re-rendering the page with its errors.
A real backend. For /submit/ in particular, a listing form implies uploads, moderation and a record write, which is a product rather than a form.
Whichever you choose, three things are not optional and none of them is in the demo: validate on the server as well as in the browser, escape anything you put into an email header or body, and rate-limit. The theme is honest about not having them rather than shipping a stub that looks finished.
The field look
Any field you add should reuse the existing look rather than re-derive it. .form__input in global.css is the design’s field — white surface, #d4eef5 border, 8px radius, cyan border on hover, cyan ring on focus-visible — and the input, textarea, select and password primitives already build on the shared _field recipe. Components covers the contract; the short version is that a hand-rolled <input class="border rounded"> will look almost right and be wrong in four states.