Skip to content

Getting Started with FormDSL (React)

Build complex, multi-section forms with a declarative JSX DSL. No backend required.

Install

bash
npm install @formdsl/react

Import the stylesheet

In your app entry point (e.g., main.tsx or layout.tsx):

tsx
import '@formdsl/react/styles'

Your first form

tsx
import { Form, Section, Question, Row } from '@formdsl/react'

export default function ContactForm() {
  return (
    <Form id="contact" version="1.0" onSubmit={(answers) => {
      console.log(answers)
      // { name: "Jane", email: "jane@example.com", phone: "555-1234" }
    }}>
      <Section title="Contact Info">
        <Question id="name" type="text" required label="Full Name" />
        <Row>
          <Question id="email" type="email" required label="Email" />
          <Question id="phone" type="tel" label="Phone" format="phone" />
        </Row>
      </Section>
    </Form>
  )
}

That's it. You get validation, error messages, and a submit button out of the box.

Add more sections

Add multiple <Section> components and pick a navigation mode:

tsx
<Form id="application" version="1.0" navigation="stepper"
  onSubmit={(answers) => saveToDatabase(answers)}>

  <Section title="Personal">
    <Question id="name" type="text" required label="Full Name" />
    <Question id="dob" type="date" required label="Date of Birth" />
  </Section>

  <Section title="Employment">
    <Question id="employer" type="text" label="Current Employer" />
    <Question id="salary" type="currency" label="Annual Salary" />
  </Section>

  <Section title="Preferences">
    <Question id="contact" type="radio" required label="Preferred Contact" options={[
      { value: 'email', label: 'Email' },
      { value: 'phone', label: 'Phone' },
      { value: 'mail', label: 'Mail' },
    ]} />
  </Section>

  <Review />
</Form>

Navigation modes: "sidebar", "stepper", "tabs", or "none" (single scrollable page).

Conditional fields

Use showIf to show/hide fields based on other answers. Uses MongoDB-style operators:

tsx
<Question id="employed" type="yesno" label="Are you employed?" />

{/* Only shown when employed = true */}
<Question id="employer" type="text" label="Employer"
  showIf={{ employed: true }} />

{/* Required only when employed */}
<Question id="jobTitle" type="text" label="Job Title"
  requiredIf={{ employed: true }} />

{/* Comparison operators */}
<Question id="seniorDiscount" type="checkbox" label="Senior discount"
  showIf={{ age: { gte: 65 } }} />

{/* OR conditions */}
<Banner variant="info" showIf={{ $or: [{ country: "US" }, { country: "CA" }] }}>
  North American residents qualify for free shipping.
</Banner>

Repeatable groups

Let users add/remove sets of fields:

tsx
<Repeat id="dependents" label="Dependents" collapsible
  itemLabel="{{firstName}} {{lastName}}" minItems={0} maxItems={10}>
  <Row>
    <Question id="firstName" type="text" required label="First Name" />
    <Question id="lastName" type="text" required label="Last Name" />
  </Row>
  <Question id="relationship" type="select" required label="Relationship" options={[
    { value: 'child', label: 'Child' },
    { value: 'spouse', label: 'Spouse' },
    { value: 'parent', label: 'Parent' },
  ]} />
</Repeat>

Calculated fields

Formulas auto-compute based on other answers:

tsx
<Question id="qty" type="number" required label="Quantity" min={1} />
<Question id="price" type="currency" required label="Unit Price" />
<Question id="subtotal" type="currency" label="Subtotal"
  formula="{{qty}} * {{price}}" />
<Question id="tax" type="currency" label="Tax"
  formula="{{subtotal}} * 0.08" />
<Question id="total" type="currency" label="Total"
  formula="{{subtotal}} + {{tax}}" />

Built-in functions: SUM, COUNT, MIN, MAX, IF, ROUND.

Layout components

tsx
import { Form, Section, Question, Row, Card, Banner, Divider } from '@formdsl/react'

<Section title="Details">
  <Banner variant="info" title="Note">
    All fields marked with * are required.
  </Banner>

  <Card title="Address">
    <Question id="street" type="text" required label="Street" />
    <Row>
      <Question id="city" type="text" required label="City" />
      <Question id="state" type="text" required label="State" />
      <Question id="zip" type="text" required label="ZIP" format="zip" />
    </Row>
  </Card>

  <Divider />

  <Question id="notes" type="textarea" label="Additional Notes" rows={4} />
</Section>

Theming

Override CSS variables to match your brand:

css
.my-form {
  --fb-primary: #e11d48;
  --fb-radius: 8px;
  --fb-font-family: 'Inter', sans-serif;
}
tsx
<Form id="styled" class="my-form" ...>

Built-in themes: "default" and "printed". See the theming reference for all CSS variables.

Field types

FormDSL includes 30+ field types:

TypeDescription
text, email, tel, urlText inputs with optional masks
textareaMulti-line text
number, currencyNumeric with formatting, formulas
date, datetime, time, year, monthDate/time pickers
select, radio, multicheckChoice fields
boolean, checkbox, yesnoTrue/false inputs
ratingStars or number scale
fileFile upload (MIME validation, max size)
signatureSignature pad
tagsTag input with autocomplete
matrixRadio grid (Likert scales)
rankingDrag-and-drop reorder
countrySearchable country picker
hiddenHidden tracking field
customBring your own component

Input masks: "ssn", "ein", "phone", "zip", "zip+4", or any custom IMask pattern.

SolidJS support

FormDSL also ships @formdsl/solid with an identical API:

tsx
import { Form, Section, Question } from '@formdsl/solid'
import '@formdsl/solid/styles'

What's next

  • Connected Mode — Add a backend for auto-save, submissions dashboard, file uploads, and webhooks
  • DSL Reference — Complete API reference for all components and props
  • Theming — CSS variables, layers, and custom themes
  • Self-Hosting — Deploy your own FormDSL server

Get in touch

FormDSL is in early access. Questions, feedback, or want to try it out? Reach out at hello@formdsl.dev.