> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Form

> Inline form bound to a schema table; writes a row and optionally fires a capability on submit.

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const FeatureTable = ({columns, rows, featuredRow, caption, variant}) => <div className={`gv-feature-table ${variant ? `gv-feature-table--${variant}` : ""}`}>
    <table>
      {caption && <caption className="gv-feature-table__caption">{caption}</caption>}
      <thead>
        <tr>
          {columns.map(col => <th key={col.key} scope="col">
              <span className="gv-feature-table__header">{col.label}</span>
            </th>)}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, i) => {
  const rowClass = [i === featuredRow ? "gv-feature-table__row--featured" : "", row.chosen ? "gv-feature-table__row--chosen" : ""].filter(Boolean).join(" ");
  return <tr key={i} className={rowClass}>
              {columns.map(col => {
    const v = row[col.key];
    if (variant === "tradeoff" && Array.isArray(v)) {
      const pill = col.key === "pros" ? "gv-feature-table__pill--pro" : col.key === "cons" ? "gv-feature-table__pill--con" : "";
      return <td key={col.key}>
                      {v.map((item, j) => <span key={j} className={`gv-feature-table__pill ${pill}`}>{item}</span>)}
                    </td>;
    }
    return <td key={col.key}>{v}</td>;
  })}
            </tr>;
})}
      </tbody>
    </table>
  </div>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="BLOCK" />

`Form` renders a self-contained form whose fields map directly to columns of a schema table. This page explains the props, how submit binds to the table, and how `on_submit` fires a capability after the write. On submit, the runtime writes a new row to the table and, optionally, fires a capability — typically `sendEmail` for lead notifications or `collectPayment` for paid sign-ups. You list the fields you want; the Builder infers each input type from the column schema.

## Example

```json form.json theme={null}
{
  "block": "Form",
  "props": {
    "schema_table": "leads",
    "fields": ["name", "email", "company"],
    "submit_label": "Send",
    "on_submit": {
      "capability": "sendEmail",
      "args": { "template": "lead-thankyou", "to": "{{form.email}}" }
    }
  }
}
```

## Props

<DefList>
  <Def term="schema_table" type="string" required>
    Name of the workspace schema table that receives the new row on submit.
  </Def>

  <Def term="fields" type="string[]" required>
    Ordered list of column names to render as form inputs. Input type is inferred from the column schema.
  </Def>

  <Def term="submit_label" type="string" defaultValue="Submit">
    Label for the submit button.
  </Def>

  <Def term="on_submit" type="{ capability, args }">
    Capability invocation fired after the row is written.
  </Def>
</DefList>

## How it binds to data

On submit, the runtime inserts a row into the table named in `schema_table` containing the values the user entered. Validation runs against the column types declared on the schema — required, type, length — before the write. Once the row is written, the runtime fires `on_submit` if it is set.

### The `on_submit` capability binding

`on_submit` takes two keys:

<ArrowList>
  <ArrowItem>`capability` — the name of the capability to invoke, e.g. `"sendEmail"`.</ArrowItem>
  <ArrowItem>`args` — a plain object passed to that capability. Any value can reference a submitted form field using the `{{form.<field>}}` template token.</ArrowItem>
</ArrowList>

For example, to send a thank-you email to the address the user typed into the `email` field:

```json on-submit.json theme={null}
{
  "capability": "sendEmail",
  "args": {
    "template": "lead-thankyou",
    "to": "{{form.email}}"
  }
}
```

The `{{form.<field>}}` tokens resolve at runtime after the row write, so they always reflect what the user actually submitted.

## Patterns

<FeatureTable
  columns={[
{ key: "use", label: "Use case" },
{ key: "wire", label: "Wiring" }
]}
  rows={[
{ use: "Lead capture", wire: "Bind to a `leads` table, fire `sendEmail` on submit to notify the team." },
{ use: "Sign-up", wire: "Bind to a `users` table; pair with `authUser` downstream to gate access." },
{ use: "Contact form", wire: "Lightweight alternative to a third-party widget — data stays in your workspace." },
{ use: "Internal-tool record creation", wire: "Let team members add rows to any table without building a custom UI." }
]}
/>

## Common props

In addition to the Form-specific props, every block accepts `id`, `class_overrides`, `visible_if`, and `analytics_event`. See [common props on the PageBlocks overview](/pageblocks/overview#common-props).

## Where to go next

<Columns cols={2}>
  <BrandCard eyebrow="BLOCK" title="Table" href="/pageblocks/blocks/table">
    Read the rows your Form is writing — paginated, filterable, sortable.
  </BrandCard>

  <BrandCard eyebrow="CAPABILITY" title="sendEmail" href="/capabilities/send-email">
    The most common `on_submit` capability — args, providers, retry behavior.
  </BrandCard>
</Columns>
