How Formily Handles Form Patterns: editable, readOnly, disabled, and readPretty
Formily uses a unified pattern property with four states—editable, readOnly, disabled, and readPretty—to control field interaction modes, which UI components translate into native HTML attributes or alternative renderings.
The alibaba/formily repository implements a declarative pattern system that determines how form fields behave and render. Instead of manually toggling HTML attributes, developers set a field's pattern property, and Formily's React renderers automatically apply the correct accessibility and visual semantics.
Understanding Formily's Pattern System
Formily models interaction states through the FieldPatternTypes enum, storing the current mode on every field instance. This approach decouples business logic from presentation, allowing patterns to cascade from forms to individual fields.
The Four Pattern States
According to the Field API documentation in packages/core/docs/api/models/Field.md, Formily supports these distinct patterns:
editable– The default state allowing full user interaction and input.readOnly– Displays the value but prevents modification; the field remains focusable.disabled– Renders the field as non-interactive and visually dimmed; maps to the nativedisabledHTML attribute.readPretty– Renders the value as static text without input widgets, effectively disabling interaction while preserving layout.
Pattern Storage in Field Models
Each field instance maintains its own pattern attribute, defaulting to "editable". The core field model exposes a setPattern() method (defined at lines 89–100 of the Field API docs) to mutate this state programmatically. When a pattern changes, Formily's reactive system triggers UI updates across all subscribed components.
How UI Components React to Patterns
Formily's React bindings inspect the field's pattern at render time to determine which props to forward to the underlying component. This mapping happens in the core renderer and specialized wrappers.
ReactiveField Renderer
In packages/react/src/components/ReactiveField.tsx (lines 86–98), the core renderer translates pattern values into native HTML attributes:
const disabled = !isVoidField(field)
? field.pattern === 'disabled' || field.pattern === 'readPretty'
: undefined;
const readOnly = !isVoidField(field)
? field.pattern === 'readOnly'
: undefined;
return React.createElement(
getComponent(field.componentType),
{
disabled,
readOnly,
...toJS(field.componentProps),
value,
onChange,
onFocus,
onBlur,
},
content,
);
When pattern is set to disabled or readPretty, the component receives disabled={true}, preventing focus. The readOnly pattern exclusively sets the readOnly attribute, allowing focus but blocking edits.
Editable Wrapper for Inline Editing
The Editable component in packages/next/src/editable/index.tsx (lines 30–45) demonstrates dynamic pattern switching between editable and readPretty:
const useEditable = (): [boolean, (payload: boolean) => void] => {
const pattern = useParentPattern();
const field = useField<Field>();
useLayoutEffect(() => {
if (pattern === 'editable') {
field.setPattern('readPretty');
}
}, [pattern]);
return [
field.pattern === 'editable',
(payload) => {
if (pattern !== 'editable') return;
field.setPattern(payload ? 'editable' : 'readPretty');
},
];
};
This hook initializes fields in readPretty mode and toggles them to editable when the user initiates inline editing, provided the parent context allows editing.
Pattern Inheritance from Parent Forms
Fields inherit patterns from their parent form or field group via the useParentPattern() hook defined in packages/react/src/components/Field.tsx:
const useParentPattern = () => {
const field = useField<Field>();
return field?.parent?.pattern || field?.form?.pattern;
};
This utility enables cascading behavior: setting form.setPattern('disabled') affects all descendants unless explicitly overridden by a field's local pattern property.
Programmatic Pattern Control
Change interaction modes imperatively using the field or form API:
import { createForm } from '@formily/core';
const form = createForm();
// Disable specific field
form.query('username').take((field) => {
field.setPattern('readOnly');
});
// Disable entire form
form.setPattern('disabled');
// Toggle between editable and readPretty
const toggleEdit = (field) => {
field.setPattern(
field.pattern === 'editable' ? 'readPretty' : 'editable'
);
};
Implementation Examples
Mixed Pattern Form
This example demonstrates global form disability with selective field overrides using the x-pattern schema attribute:
import { createForm } from '@formily/core';
import { createSchemaField, FormProvider } from '@formily/react';
import { Input, FormItem } from '@formily/antd';
const form = createForm({
pattern: 'disabled', // Global default
});
const SchemaField = createSchemaField({
components: { Input, FormItem },
});
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="username"
title="Username"
x-pattern="editable" // Override global disabled state
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="email"
title="Email"
x-pattern="readOnly" // Native readOnly attribute
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="role"
title="Role"
x-pattern="readPretty" // Rendered as static text
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
</FormProvider>
);
Click-to-Edit with Editable
Implement inline editing that switches between display and input modes:
import { createForm } from '@formily/core';
import { FormProvider, Editable } from '@formily/next';
const form = createForm({
pattern: 'editable',
});
export default () => (
<FormProvider form={form}>
<Editable.Popover title="Name">
<span>{form.values.name || 'Click to edit'}</span>
</Editable.Popover>
</FormProvider>
);
The Editable.Popover component internally calls field.setPattern('editable') on activation and reverts to readPretty on blur or submission.
Summary
- Formily stores interaction modes in the
patternproperty using four states:editable,readOnly,disabled, andreadPretty. - The
ReactiveFieldrenderer inpackages/react/src/components/ReactiveField.tsxmaps these patterns to nativedisabledandreadOnlyHTML attributes. readPrettymode disables the field while rendering values as static text,区别于readOnlywhich preserves focus.- Patterns inherit from parent forms through
useParentPattern(), allowing global state management with local overrides. - Use
field.setPattern()orform.setPattern()to programmatically control interaction states.
Frequently Asked Questions
What is the difference between readOnly and disabled in Formily?
readOnly renders the field as non-editable but keeps it focusable and included in form tab order, while disabled removes the field from focus and typically applies visual dimming. In packages/react/src/components/ReactiveField.tsx, readOnly maps to the readOnly HTML attribute, whereas disabled maps to the disabled attribute.
How do I disable an entire form in Formily?
Call form.setPattern('disabled') on the form instance. This pattern cascades to all child fields via the useParentPattern() hook, unless individual fields explicitly override their own pattern property.
Can individual fields override the form's global pattern?
Yes. Field-level patterns take precedence over inherited states. In schema definitions, use the x-pattern property (e.g., x-pattern="editable") to force a specific interaction mode regardless of the parent form's setting.
What is readPretty mode used for?
readPretty displays field values as read-only text without input widgets, often used in "click-to-edit" interfaces. According to the source in packages/next/src/editable/index.tsx, this pattern disables the field while presenting a cleaner visual representation of the data until the user activates edit mode.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →