falcondev-form
Building or editing forms with @falcondev-oss/form — the type-safe, reactive, schema-driven form library (packages form-core, form-react, form-vue) built on…
Skill by falcondev-oss
Chalk Labs
Need help deploying falcondev-form?
Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/falcondev-oss-falcondev-form
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: frontend, data, writing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/falcondev-form
What's inside
- Skill name
falcondev-form- Description
- Building or editing forms with @falcondev-oss/form — the type-safe, reactive, schema-driven form library (packages form-core, form-react, form-vue) built on @vue/reactivity. Use when working with useForm / useFormCore, form.fields accessors, $use(), field.value / field.handleChange / field.model, form.data, or a Standard Schema (zod v4 / arktype) form in a React or Vue codebase.
- License
- MIT
- Instructions
- 162 lines in SKILL.md · view on GitHub
Files (3)
- SKILL.md
- frameworks.md · reference/
- patterns.md · reference/
SKILL.md (first lines)
# @falcondev-oss/form
A framework-agnostic form-state library on top of `@vue/reactivity`. You give it a **Standard Schema** (zod v4, arktype, …) and reactive **sourceValues**; it gives you a reactive form **handle** with a tree of field **accessors**.
Three published packages — pick the one for the runtime, never import `-core` directly in app code:
| Package | Entry | Adds |
|---|---|---|
| `@falcondev-oss/form-core` | `useFormCore`, all types, `/reactive` helpers | engine; use directly only outside React/Vue |
| `@falcondev-oss/form-react` | `useForm`, `useField`, `FormFieldMemo` | React re-render integration, `field.model = {value,onUpdate}` |
| `@falcondev-oss/form-vue` | `useForm`, `useFormHandles` | `field.model` writable computed (`v-model`) |
`useForm` from the framework package wraps `useFormCore` — same options, same handle. Everything below is identical across frameworks **except** field binding and re-render (see `reference/frameworks.md`).
## Two ideas that explain everything
1. **Accessors are lazy paths; `$use()` materializes.** `form.fields.address.city` is just a typed path proxy — no field object exists yet. Calling `.$use()` creates (and caches) the actual reactive `FormField`. Navigate with dot/`.at()`, then `.$use()` at the leaf you bind.
2. **Form data is `NullableDeep`.** While editing, *every* value can be `null` — a half-filled form has nulls everywhere. So `form.data`, `field.value`, and accessor types are the schema's **input** type made deeply nullable (objects → `T | null`, arrays → `(T|null)[] | null`). The validated **output** type (non-null) only appears in `submit({ values })`. Write UI against nullable types; trust `submit` for clean data.
## Golden path
```ts
import { useForm } from '@falcondev-oss/form-vue' // or -react
import z from 'zod'
const form = useForm({
schema: z.object({ name: z.string(), age: z.number() }),
sourceValues, // initial data (NullableDeep); a value, getter, or ref — React needs a stable reference
async submit({ values }) {
// `values` is validated OUTPUT: { name: string, age: number }
await api.save(values)
// return nothing → { success: true }; return { success: false } to keep form dirty
},
})
const nameField = form.fields.name.$use()
nameField.handleChange('Jane') // programmatic write (validates, marks dirty)
// or write straight through the reactive data:
form.data.name = 'Jane' // equivalent effect
await form.submit() // validates whole schema, runs submit if valid
```
## `useForm(options)`
- `schema` — any **Standard Schema** that *also* exposes Standard JSON Schema (zod v4, arktype). JSON Schema drives `field.schema` metadata and required-detection. Non-JSON-representable types (Date, bigint, Map…) need a codec/transform or metadata extraction silently degrades (a console warning fires).
- `sourceValues` — initial `NullableDeep` data. A value, a getter `() => data`, or (Vue) a `ref`. In **React** it must be a *stable reference* — see below. May be `undefined` → form is **pending** (`isLoading`/`field.isPending` true, `data` is `undefined`, writes ignored) until it resolves. When it later changes: if the form is **not dirty**, the form resets to the new values; if dirty, the update is skipped with a warning (except during submit).
- `submit({ values })` — `async`, receives the **validated output**. Return `void`/`{success:true}` → form marked pristine; `{success:false}` → stays dirty. **`await` everything that must finish before the form settles** — most importantly cache/query invalidations, so fresh data flows back into `sourceValues` before the form is marked pristine (an un-awaited invalidation lets the form settle against stale data). With TanStack Query: `await queryClient.invalidateQueries(...)` directly in `submit`, or do it in the mutation's `onSuccess` and `await mutateAsync(...)` in `submit` (awaiting the mutation awaits `onSuccess`).
- `disabled?` — `boolean | Ref | getter`. Blocks `handleChange`/`handleBlur`/`reset` on all fields.
- `hooks?` — lifecycle hooks (see `reference/patterns.md`).
### Writing `sourceValues` (do it this way)
Three ordered branches: still-loading → existing entity → blank defaults. Vue takes them as a getter; React takes them as a `useMemo`ed value.
```ts
// Vue — getter, tracked reactively
useForm({
schema,
sourceValues: () => {
if (isLoading.value) return undefined // 1. source still loading → form is pending
if (entity.value) return entity.value // 2. edit: return the whole entity object
return { name: null, age: null } // 3. create: blank defaults
},
async submit({ values }) { /* … */ },
})
```
```tsx
// React — same branches, wrapped in useMemo
const sourceValues = useMemo(() => {
if (isLoading) return undefined
if (entity) return entity
return { name: null, age: null }
}, [isLoading, entity])
useForm({ schema, sourceValues, async submit({ values }) { /* … */ } })
```
**In React `sourceValues` must be a stable reference.** Never inline the object (or a getter) in the `useForm` call: React re-runs the component body every render, so a fresh object identity re-seeds the form on every render — resetting a clean form and warning on a dirty one. Memoize it (`useMemo`), or hoist a constant to module scope, or pass query data directly (`sourceValues: query.data` — already stable). Getters are worse than useless here: the adapter captures the getter **once** on mount, so one built over React state or props is frozen at its first-render closure and never sees an update.
- **Return `undefined` while the source is loading** (e.g. the fetch for the entity being edited is pending). This puts the form in the pending state instead of seeding it with a wrong/empty shape you'd have to overwrite later.
- **Return the existing entity as one whole object** — don't hand-build `{ name: entity?.name, age: entity?.age, … }` with optional chaining per key. If the entity doesn't match the schema, spread and override just the divergent fields: `return { ...entity, tags: entity.tags ?? [] }`.
- **Use `null` for fields with no meaningful default** — not `''` or `0` just because the type is string/number. `NullableDeep` makes `null` valid for every field; an empty string is a *real value* that reads as "the user typed nothing", which is different from "untouched".
## The form handle
| Member | Type | Notes |
|---|---|---|
| `form.data` | `NullableDeep<T> \| undefined` | reactive; **read and write** directly (`form.data.x = …`). `undefined` while pending. |
| `form.fields` | accessor tree | navigate then `.$use()` |
| `form.isDirty` | `boolean` | any change made (edit count ≠ 0), reset by `submit` success / `reset` |
| `form.isChanged` | `boolean` | deep-equals current data vs `sourceValues` (false if edited back to original) |
| `form.isLoading` | `boolean` | submitting **or** pending source values |
| `form.isDisabled` | `boolean` | loading or `disabled` option |
| `form.errors` | `Issue[] \| undefined` | all validation issues, or undefined |
| `form.submit()` | `() => Promise<{success:boolean}>` | validates all, runs `submit` |
| `form.reset()` | `() => void` | restore to `sourceValues` |
| `form.hooks` | hookable | `hook`/`hookOnce`/`addHooks` |
## Field accessors
```ts
form.fields.user.email.$use() // nested object
form.fields.tags.at(0).$use() // array item (negative index ok: .at(-1))
form.fields.tags.$use() // the array field itself
for (const item of form.fields.tags) item.$use() // iterate (reactive)
form.fields.tags.delete(field.key) // remove array item by its key (see below)
form.fields.$use() // root field = whole form value
form.fields['a.b'].$use() // keys containing dots work (auto-escaped)
```
`.at(i)` accessor exists even before the array/index does — `.$use().value` is `null` until data arrives. `.delete(key)` **must** target an array item's key (`items[2]`), not a nested property — otherwise it throws.
## The `FormField` (result of `$use()`)
| Member | Notes |
|---|---|From falcondev-oss/skills, MIT.
More from falcondev-oss/skills
- 0.598score
workflow
falcondev-oss
Durable, type-safe queue workers with @falcondev-oss/workflow on Redis.
SkillAFreshness: A★ 2Claude Code +9 - 0.598score
conventional-commits
falcondev-oss
Format commit messages, branch names, PR titles, and issue titles to the Conventional Commits 1.0.0 spec.
SkillAFreshness: A★ 2Claude Code +9 - 0.597score
babysit-pr
falcondev-oss
Monitor a pull request through review and CI.
SkillAFreshness: A★ 2Claude Code +9 - 0.597score
file-pr
falcondev-oss
File a PR from the current branch.
SkillAFreshness: A★ 2Claude Code +9 - 0.593score
bug-review
falcondev-oss
Cold review of a diff for merge-blocking bugs, each with a way to demonstrate the failure.
SkillAFreshness: A★ 2Claude Code +9 - 0.592score
review-local-changes
falcondev-oss
Review the local changes cold and report the findings to the user.
SkillAFreshness: A★ 2Claude Code +9 - 0.592score
implement-pr
falcondev-oss
Implement a change and deliver a green, review-ready pull request with explicit scope and evidence.
SkillAFreshness: A★ 2Claude Code +9 - 0.584score
quick-iteration
falcondev-oss
Enter a quick iteration session.
SkillAFreshness: A★ 2Claude Code +9 - 0.554score
shareflare
falcondev-oss
Share any local file through a public URL.
SkillBFreshness: B★ 2Claude Code +9 - 0.507score
trpc-typed-form-data
falcondev-oss
Type-safe file uploads through tRPC with @falcondev-oss/trpc-typed-form-data.
SkillCFreshness: C★ 2Claude Code +9
Signals
- Stars
- 2
- Forks
- 0
- Open issues
- 0
- Last commit (this folder)
- Aug 11, 2026 (2 months ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/falcondev-oss/skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from falcondev-oss
- 0.598score
workflow
falcondev-oss
Durable, type-safe queue workers with @falcondev-oss/workflow on Redis.
SkillAFreshness: A★ 2Claude Code +9 - 0.598score
conventional-commits
falcondev-oss
Format commit messages, branch names, PR titles, and issue titles to the Conventional Commits 1.0.0 spec.
SkillAFreshness: A★ 2Claude Code +9 - 0.597score
babysit-pr
falcondev-oss
Monitor a pull request through review and CI.
SkillAFreshness: A★ 2Claude Code +9 - 0.597score
file-pr
falcondev-oss
File a PR from the current branch.
SkillAFreshness: A★ 2Claude Code +9 - 0.593score
bug-review
falcondev-oss
Cold review of a diff for merge-blocking bugs, each with a way to demonstrate the failure.
SkillAFreshness: A★ 2Claude Code +9 - 0.592score
review-local-changes
falcondev-oss
Review the local changes cold and report the findings to the user.
SkillAFreshness: A★ 2Claude Code +9
Done for you
Rather not set it up yourself?
Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.
See how we build AI agents →Read the case studies
Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.
- launches shipped
- 50+
- launches shipped
- media placements
- 500+
- media placements
- founders on every account
- 2
- founders on every account
Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block
Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.
Get falcondev-form running in your business
Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.
Cite this page
falcondev-form skill. Agent Mart. https://agentmart.dev/skills/falcondev-oss-falcondev-form (last updated 2026-10-24)