# Quickstart

URL: https://docs.forms.saastro.io/docs/quickstart
> Build and render your first working form in five minutes.

# Quickstart

Build a working contact form in five minutes: install the package, auto-discover your shadcn/ui components, define three fields with `FormBuilder`, render `<Form>`, and read the submitted values.

**Prerequisites:** a React project on Vite with [shadcn/ui](https://ui.shadcn.com) initialized (a `components.json` and the `@/` path alias). Not on Vite? Everything still works — you just pass components explicitly instead of using glob auto-discovery (see [Installation](/docs/installation) and [Components](/docs/components)).

## 1. Install

```bash
# Install the package
npm install @saastro/forms

# Install required shadcn components + missing peer dependencies
npx @saastro/forms install-deps
```

`install-deps` reads your `components.json`, adds any required shadcn components, and installs missing peer dependencies (`zod`, `react-hook-form`, `react-day-picker`, `date-fns`). See [Installation](/docs/installation) for manual setup and the [CLI reference](/docs/installation/cli) for all flags.

## 2. Define and render the form

Create `src/components/ContactForm.tsx`:

```tsx

// Auto-discover every shadcn component in src/components/ui (Vite only).
// `eager: true` is required — lazy globs are not supported.
const uiComponents = import.meta.glob('@/components/ui/*.tsx', { eager: true });

const config = FormBuilder.create('contact')
  .addField('name', (f) =>
    f.type('text').label('Name').placeholder('Jane Doe').required('Name is required'),
  )
  .addField('email', (f) =>
    f.type('email').label('Email').required().email('Please enter a valid email'),
  )
  .addField('message', (f) => f.type('textarea').label('Message').rows(4).required())
  .addStep('main', ['name', 'email', 'message'])
  .buttons({ submit: { type: 'submit', label: 'Send' } })
  .successMessage('Thanks! We got your message.')
  .onSuccess((values) => {
    // Fires after a successful submit
    console.log(values); // { name: '...', email: '...', message: '...' }
  })
  .build();

  return <Form config={config} components={uiComponents} />;
}
```

Then render it anywhere in your app:

```tsx

  return (
    <main className="mx-auto max-w-md p-8">
      <ContactForm />
    </main>
  );
}
```

Run your dev server — you have a validated, type-safe form.

A few things worth knowing about what you just wrote:

- **Every form needs at least one step.** Even single-page forms declare one (`.addStep('main', [...])`); `build()` throws without it. Multi-page wizards just add more steps — see [Multi-Step Forms](/docs/multi-step).
- **Validation is declarative.** `.required()` and `.email()` build a JSON-serializable rules object that is compiled to Zod at runtime. You can also pass a Zod schema directly with `.schema()` — see [Validation](/docs/validation).
- **`import.meta.glob` is a Vite feature.** It does not exist in Next.js or webpack. On other bundlers, pass components explicitly: `components={{ Input, Button, ... }}` — see [Components](/docs/components).
- **Default UI strings are in English.** Out of the box the submit button says `Submit` and the success panel says `✅ Thank you!` — the example sets `label: 'Send'` and a custom `successMessage` only to show how to override them. Building in another language? Flip every default globally (e.g. Spanish: `setDefaultMessages(es)`) or translate per locale — see [Internationalization](/docs/i18n).

## 3. Read the submitted values

Click **Send**. The form validates, runs the submit pipeline, replaces itself with the success message, and calls your `.onSuccess()` callback with the final values — a `Record<string, unknown>` keyed by field name, after any field transforms have been applied. With no submit destination configured (like this quickstart), nothing is sent anywhere — open the console to see the values.

> **Use `.onSuccess()`, not the `<Form onSubmit>` prop.** The rendered submit button intentionally bypasses native form submission (so multi-step navigation and confirmation flows can intercept it), which means the `onSubmit` prop only fires on native submission — for example, pressing Enter. `.onSuccess()` fires on every successful submit, whichever way it was triggered.

## 4. Send the data somewhere

To actually deliver the submission, add a submit action to the builder chain before `.build()`:

```tsx
  .submitAction(
    'save',
    {
      type: 'http',
      name: 'Save lead',
      endpoint: { url: 'https://api.example.com/leads', method: 'POST' },
      body: { format: 'json' },
    },
    'onSubmit',
  )
```

Submit actions support HTTP requests, webhooks, email providers, and custom handlers, with field mapping, conditions, and retries — see [Submit](/docs/submit). Don't have a backend? The hosted submit service handles storage, spam protection, and file uploads for you — see [HubForm](/docs/hub-form).

## Next steps

- [Installation](/docs/installation) — manual setup, non-Vite projects, the CLI.
- [FormBuilder](/docs/formbuilder) — the full fluent API, JSON configs, and serialization.
- [Validation](/docs/validation) — declarative rules, Zod schemas, custom validators.
- [Multi-Step Forms](/docs/multi-step) — wizards with conditional step routing.
- [Field Types](/fields) — interactive examples of all 34 field types.
