enforma
v0.1.1
Published
Healthy forms for React — declare your fields, enforma handles the rest
Maintainers
Readme
enforma
Healthy forms for React. Write only your business logic — enforma handles the rest.
Why Enforma
Only write what's yours. No state management, no touched/error tracking, no blur handlers. Declare your fields, validations, and submit logic — enforma handles the plumbing.
Obvious markup. A single <TextInput> renders the wrapper, label, input, error message, description, and aria — exactly as your UI library expects.
Your form logic doesn't change when your UI does. Switch to a different component library later — your form code is untouched.
Installation
npm install enformaRequires React 18+. Enforma has no UI of its own — you need a component adapter to render fields. See enforma-mui for the Material UI adapter.
Setup
Register a component adapter once before rendering any forms, typically in your app entry point:
import { registerComponents } from 'enforma';
import muiComponents from 'enforma-mui';
registerComponents(muiComponents, { variant: 'outlined' });Usage
import Enforma from 'enforma';
export function CheckoutForm() {
return (
<Enforma.Form
onSubmit={(values) => fetch('/api/order', { method: 'POST', body: JSON.stringify(values) })}
>
<Enforma.Select bind="method" label="Delivery method">
<Enforma.Select.Option value="delivery" label="Delivery" />
<Enforma.Select.Option value="pickup" label="Pickup in store" />
</Enforma.Select>
<Enforma.TextInput
bind="address"
label="Delivery address"
disabled={({ method }) => method !== 'delivery'}
validate={(value, { method }) =>
method === 'delivery' && !value ? 'Address is required' : null
}
/>
<Enforma.Submit>Place order</Enforma.Submit>
</Enforma.Form>
);
}Submit button
Use submitDisabled to control when the submit button is enabled based on form state:
import Enforma, { submitDisabled } from 'enforma';
<Enforma.Submit disabled={submitDisabled((_, __, { formValid }) => !formValid)}>
Place order
</Enforma.Submit>Features
- High performance — each field re-renders only when its own value changes (powered by
useSyncExternalStore) - Reactive attributes —
disabled,label,placeholderaccept static values or functions that respond to form state - Cross-field validation — validators receive the full form state
- Hierarchical scopes — nest sections with automatic path prefixing via
Enforma.Scope - Dynamic lists — field arrays with
Enforma.List - UI library agnostic — swap your component library without touching form logic
Advanced
For power users building custom components or integrations.
Use useFieldProps to build components that integrate with the form store:
import { useFieldProps } from 'enforma';
function MyInput({ bind, label }: { bind: string; label: string }) {
const { value, setValue, error } = useFieldProps({ bind, label });
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}CJS usage
When using CommonJS require, access the default export via .default:
const { default: Enforma, registerComponents } = require('enforma');License
MIT
