Part of the lingo docs (natural-language quantities, units, dates parser) — index: https://lingo.pascal.app/llms.txt

## Inputs

The DOM entry turns an input into a headless natural-language field. It ships no styles. It exposes state with ARIA and data attributes, adds a hidden input when `name` is supplied, canonicalizes on blur/Enter/submit, and supports programmatic `set()`, `commit()`, `update()`, and `destroy()`.

~~~ts
import { lingoInput } from "@pascal-app/lingo/dom"

const field = lingoInput(document.querySelector("#height"), {
  kind: "length",
  unit: "m",
  name: "height_m",
  errorElement: "#height-error",
  hintElement: "#height-hint",
})

field.set("6ft")
field.commit()
~~~

The React entry is a small hook over the DOM field contract.

~~~tsx
import { useLingoInput } from "@pascal-app/lingo/react"

function HeightField() {
  const field = useLingoInput({
    kind: "length",
    unit: "m",
    name: "height_m",
  })

  return <input ref={field.ref} placeholder="5'11\" or 180cm" />
}
~~~

Caption: Fields never rewrite while typing; commits canonicalize once.

Form demo rules:

- React hook: One ref carries state, value, and programmatic control.
- Vanilla controller: The DOM contract stays headless outside React.
- Native validation: Native validation blocks submit after parser commit.
- Display modes: Canonical rewrites; echo formats; preserve keeps user text.
- Constraints: Bounds fail at commit with spans on original text.
- Range vs single: A rejected range remains available as a candidate.
- Bidirectional range slider: Text parses to thumbs; pointer and keyboard movement humanizes back to text.
- Agent and hidden value: Automation sets visible text; FormData submits canonical value.

Deep link: `/docs#forms-range-slider` opens the bidirectional range slider directly.

The range slider field binds `parseRange(text, { kind: "mass", unit: "kg" })` to a two-thumb kg slider (0 to 30 kg, 0.1 kg step). Valid text such as `8-12kg`, `8 to 12 kg`, `around 10kg`, `10kg +/-2`, or a single `10kg` moves the thumbs. Pointer and keyboard changes render humanized range text, but a focused input is never rewritten.

### Web component

`defineLingoInput()` from `@pascal-app/lingo/element` registers a form-associated `<lingo-input>` custom element wrapping the same field through `ElementInternals` instead of a hidden input. `FormData`, native labels, and `:invalid` behave like any control in Vue, Svelte, Angular, or plain HTML.

DOM options documented by the public API include `kind`, `unit`, `displayUnit`, `display`, `strictness`, `accept`, `tolerance`, `escalate`, `min`, `max`, `required`, `name`, `errorElement`, `hintElement`, `formatCandidate`, `validationBehavior`, `messages`, and `debounce`.

Human docs: https://lingo.pascal.app/docs#forms