---
title: "Core concepts"
description: "The mental model behind NotForm: the schema, the form instance, fields, paths, the initial-values baseline, and what happens on every keystroke and submit."
canonical_url: "https://notformdocs.vercel.app/docs/getting-started/core-concepts"
---

> Index of every page on this site: [llms.txt](https://notformdocs.vercel.app/llms.txt)

Everything in NotForm follows from a handful of ideas. Read this page once and the rest of the documentation becomes predictable.

## The schema is the source of truth

You pass an **object** schema to `useForm`. NotForm derives three things from it:

- the type of `form.values`, which is the schema's **input**;
- the paths that `form.field()` and `form.arrayField()` accept;
- the type of the data that `onSubmit` receives, which is the schema's **output**.

The schema also performs all validation. NotForm has no rule syntax of its own.

Input and output can differ. A schema that turns the text `'42'` into the number `42` has input `{ age: string }` and output `{ age: number }`. Inputs bind to the values users edit. Outputs are what your code receives. See [Schema input and output](https://notformdocs.vercel.app/docs/guides/schema-input-output).

## One form instance owns the state

`useForm` returns a reactive form instance. It owns:

- `values`: the live, editable data;
- `errors`: the current list of validation issues;
- state flags such as `isDirty`, `isSubmitting`, and `isValidating`;
- actions such as `submit`, `validate`, `reset`, `setError`, and `setValue`.

You read state directly (`form.isDirty`, not `form.isDirty.value`) and pass the instance to `<Form :form="form">`.

## Fields are handles, not copies

`form.field('email')` does not store a value. It returns a handle whose `model` reads and writes `form.values.email`, and whose `errors`, `isTouched`, `isDirty`, and events are scoped to that path.

Fields are **cached by path**. Calling `form.field('email')` again, including on every render, returns the same instance with the same `id`. That is why touched state and pending validation survive re-renders.

Consequence: the optional configuration passed to `form.field(path, config)` is read **once**, on the first call for that path.

`Field` is a convenience component that hands a field to its slot. It is optional. A field is also usable straight from script.

## Paths are strings; issue paths are segments

You address values with dot-notation **paths**: `'email'`, `'address.city'`, `'contacts.0.email'`. Paths are checked against the schema input.

Validators report where an issue occurred as an array of **segments**: `['address', 'city']`. When you set errors yourself, you write segments:

```ts
form.setError({ message: 'Already taken.', path: ['username'] })
```

An issue with no `path` applies to the whole form.

## Initial values are a baseline

`initialValues` and `initialErrors` define the baseline. The form compares against it to compute `isDirty`, and `reset()` restores it. `reset(values, errors)` replaces the baseline, which is how you load saved data into an edit form.

## What happens when a user types

1. The input updates `field.model`, which writes into `form.values`.
2. The input's event (`input`, `change`, or `blur`) calls the matching handler in `field.events`.
3. The handler checks the field's configuration: is this event enabled, or is the field in eager mode with an existing error?
4. If a debounce is configured, validation waits for a pause. Blur never waits.
5. NotForm validates a snapshot of the whole form with your schema.
6. Only the issues at this field's exact path replace the field's errors.
7. If the user changed any value while validation was running, the result is discarded as **stale** and the errors are left alone.

Details are in [Validation](https://notformdocs.vercel.app/docs/guides/validation).

## What happens when a user submits

1. If a submit is already running, the new one is ignored.
2. `isSubmitting` becomes `true` and `submitCount` increases. Every field now counts as touched.
3. The whole form is validated.
4. If a value changed while validating, validation runs again.
5. If the schema reports issues, they replace all errors and `onSubmit` is **not** called.
6. If it passes, errors are cleared and `onSubmit` is awaited with the validated output.
7. `isSubmitting` returns to `false`, even if `onSubmit` throws.

Details are in [Submission and reset](https://notformdocs.vercel.app/docs/guides/submission-and-reset).

## Glossary

<table>
<thead>
  <tr>
    <th>
      Term
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <strong>
        Schema input
      </strong>
    </td>
    
    <td>
      The shape of <code>
        form.values
      </code>
      
      : what users edit, before parsing.
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Schema output
      </strong>
    </td>
    
    <td>
      The shape <code>
        onSubmit
      </code>
      
       receives: the parsed result.
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Path
      </strong>
    </td>
    
    <td>
      A dot-notation string such as <code>
        'address.city'
      </code>
      
      . Used with <code>
        form.field()
      </code>
      
       and <code>
        form.setValue()
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Segments
      </strong>
    </td>
    
    <td>
      A path as an array, such as <code>
        ['address', 'city']
      </code>
      
      . Used by issues.
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Issue
      </strong>
    </td>
    
    <td>
      One validation problem: a <code>
        message
      </code>
      
       and an optional <code>
        path
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Field instance
      </strong>
    </td>
    
    <td>
      The cached handle returned by <code>
        form.field(path)
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Array field
      </strong>
    </td>
    
    <td>
      A field for an array, with <code>
        items
      </code>
      
       and mutation methods. Returned by <code>
        form.arrayField(path)
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Item
      </strong>
    </td>
    
    <td>
      One entry of an array field. It is a field for its own value and has an <code>
        index
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Baseline
      </strong>
    </td>
    
    <td>
      The current <code>
        initialValues
      </code>
      
       and <code>
        initialErrors
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Touched
      </strong>
    </td>
    
    <td>
      A field was blurred, or a submit was attempted.
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Dirty
      </strong>
    </td>
    
    <td>
      Current values differ from the baseline.
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Stale
      </strong>
    </td>
    
    <td>
      A validation result that was made obsolete by a newer run or a value change. It is discarded.
    </td>
  </tr>
</tbody>
</table>

## Next steps

- [Binding inputs](https://notformdocs.vercel.app/docs/guides/binding-inputs) shows how to connect every kind of control.
- [Validation](https://notformdocs.vercel.app/docs/guides/validation) explains how to tune the behavior described above.

## Sitemap

See the full [sitemap](https://notformdocs.vercel.app/sitemap.md) for all pages.
