@formkit/core 2.1.2
Framework-agnostic core of FormKit. Every input, group, list, and form is a
FormKitNode in a tree. Vue and React adapters (@formkit/vue,
@formkit/react) render these nodes; input types, validation, and i18n live in
sibling packages, not here. Scope this Skill to @formkit/core imports only.
- Package:
@formkit/core@2.1.2, ESM ("type": "module") with CJS fallback, plus adevelopmentcondition build (package.json:8-21). - Sole dependency:
@formkit/utils@^2.1.2(package.json:38-40). - Docs: https://formkit.com/essentials/architecture
- API reference: https://formkit.com/api-reference/core
When to use another package
| Need | Package |
|---|---|
<FormKit> components, defaultConfig, createInput | @formkit/vue / @formkit/react |
Input type definitions (text, select…) | @formkit/inputs |
| Validation rules | @formkit/validation |
| Locale messages | @formkit/i18n |
| Schema rendering | @formkit/vue (types + compiler are here) |
Public API
Runtime exports (dist/index.mjs:1919): bfs, clearErrors, compile,
createClasses, createConfig, createMessage, createNode,
createPlaceholder, createValue, deregister, error, errorHandler,
generateClassList, getNode, isComponent, isConditional, isDOM,
isList, isNode, isPlaceholder, isSugar, names, register, reset,
resetCount, resetRegistry, setErrors, stopWatch, submitForm,
sugar, use, useIndex, valueInserted, valueMoved, valueRemoved,
warn, warningHandler, watchRegistry, FORMKIT_VERSION — plus ~70 public
types (dist/index.d.mts:2329).
Common tasks
Create a node tree
import { createNode } from '@formkit/core'
const email = createNode({ name: 'email' })
const form = createNode({
type: 'group', // 'input' (default) | 'group' | 'list'
children: [email, createNode({ name: 'password' })],
})
form.at('email') // → the email node
Group values are objects keyed by child name; list values are arrays keyed
by child index (dist/index.mjs:714-723). Full option table:
node API.
Set a value and await settlement
await email.input('sam@example.com') // debounced, async commit
email.value // committed value
Never assign node.value = x directly. input() debounces (default 20 ms on
input nodes, dist/index.mjs:1255-1267), then commits through the commit
hook (dist/index.mjs:759-765). await node.settled resolves when the whole
subtree finished committing (dist/index.d.mts:1856-1860).
Listen to events (and clean up)
const receipt = form.on('commit.deep', ({ payload }) => { ... })
// later:
form.off(receipt)
.deep catches events bubbling from descendants (dist/index.mjs:47-53).
on() returns a receipt; always pass it to off(). Core event table:
events and hooks.
Register hook middleware
node.hook.commit((value, next) => next(transform(value)))
Middleware signature is (payload, next) => next(payload); hooks:
classes, commit, error, setErrors, init, input, message,
prop, text, schema (dist/index.d.mts:579-600). Register inside plugins
for reuse.
Write a plugin
import { createNode } from '@formkit/core'
import type { FormKitPlugin } from '@formkit/core'
const colorize: FormKitPlugin = (node) => {
node.props.color = node.type === 'group' ? 'yellow' : 'teal'
// return false to stop inheritance by children
}
createNode({ type: 'group', plugins: [colorize], children: [createNode()] })
Plugins are inherited by descendants and run once per node. Attach
plugin.library to define custom input types via node.define().
Store a message; count with the ledger
import { createMessage, createNode } from '@formkit/core'
const node = createNode()
node.store.set(createMessage({ key: 'limit', value: 'Too many!' }))
node.store.limit.value // 'Too many!'
node.ledger.count('visible', (m) => m.visible)
node.on('count:visible', ({ payload }) => console.log(payload))
Details: store and ledger.
Reach a node by id
import { createNode, getNode, watchRegistry, stopWatch } from '@formkit/core'
const node = createNode({ props: { id: 'manufacturer' } }) // root nodes register
getNode('manufacturer') // FormKitNode | undefined
const receipt = watchRegistry('manufacturer', (e) => { ... })
stopWatch(receipt)
Root nodes (or nodes with props.alias) register automatically
(dist/index.d.mts:2220-2228). Details:
registry and helpers.
Set errors, reset, submit
import { setErrors, clearErrors, reset, submitForm } from '@formkit/core'
setErrors('myForm', 'Server rejected', { email: ['Already taken'] })
reset('myForm', { email: '' }) // resetTo becomes the new initial value
clearErrors('myForm', true) // true clears children too
submitForm('myForm') // dispatches a submit DOM event
Compile a schema expression
import { compile } from '@formkit/core'
const condition = compile("$name == 'bob'").provide((tokens) => {
return { name: () => nameRef.value } // each token must return a function
})
condition() // false
Operators: && || == != === !== > < >= <= + - * / % (dist/index.mjs:1574-1598).
Schema node types and guards: schema and compiler.
Behavioral rules that prevent bugs
- Read config through
node.props, notnode.config; explicit props and parent config are merged into props (dist/index.d.mts:729-792). - Setting a root config property emits
config:{prop}on the origin andprop/prop:{prop}on every inheriting node that does not override it (dist/index.mjs:1283-1289). groupnodes require object values,listnodes require arrays; violations throw error 107 / 108 (dist/index.mjs:745-757).- Messages are immutable (
Readonly<FormKitMessageProps>,dist/index.d.mts:70-75); replace viastore.set(), never mutate. node.walk()traverses the subtree without the node itself and is expensive; reserve it for rare lifecycle moments (dist/index.d.mts:1885-1890).- Avoid two same-
namesiblings under one parent; if unavoidable, sync them withconfig.mergeStrategy = { fieldName: 'synced' }(dist/index.d.mts:721-725). node.roottraverses on every read; do not call it in hot paths (dist/index.d.mts:1840-1843).- Replace core error handling by registering middleware on the exported
errorHandler/warningHandlerdispatchers (dist/index.d.mts:2151-2157).
Version limits
- Written against
@formkit/core@2.1.2(published 2026-07-24). - 2.x changes since 2.0.0 are bug fixes in reset, registry cleanup, and the compiler; no core API breaks. See API changes.
FORMKIT_VERSIONis a build-time placeholder ("__FKV__") in the prepared source; do not branch on it (dist/index.d.mts:2321-2327).
References
- Node API — creation, types, traps, value flow, settlement, traversal
- Events and hooks — emitter, receipts,
.deep, event table, hook middleware - Store and ledger — messages,
createMessage, counters - Config, props, plugins — inheritance,
createConfig,mergeStrategy, libraries - Schema and compiler — schema node types, guards,
sugar(),compile() - Registry and helpers —
getNode,setErrors,reset,submitForm, error codes - API changes — 2.0.0 → 2.1.2