Element Plus Form Component
Form consists of input, radio, select, checkbox and so on. With form, you can collect, verify and submit data.
When to Invoke
Invoke this skill when:
- User needs to create a form with multiple input fields
- User wants to implement form validation
- User needs to handle form submission
- User wants to create inline forms
- User asks about form item layout and alignment
- User needs dynamic form items (add/delete)
Features
- Flexible Layout: Inline, vertical, or horizontal label alignment
- Comprehensive Validation: Built-in validation with async-validator
- Custom Rules: Support for custom validation functions
- Dynamic Items: Add or remove form items dynamically
- Size Control: Consistent sizing for all form components
- Accessibility: ARIA support for screen readers
- Error Display: Inline or tooltip error messages
API Reference
Form Attributes
| Name |
Description |
Type |
Default |
| model |
Data of form component |
Record<string, any> |
— |
| rules |
Validation rules of form |
FormRules |
— |
| inline |
Whether the form is inline |
boolean |
false |
| label-position |
Position of label |
'left' | 'right' | 'top' |
right |
| label-width |
Width of label |
string | number |
'' |
| label-suffix |
Suffix of the label |
string |
'' |
| hide-required-asterisk |
Whether to hide required asterisk |
boolean |
false |
| require-asterisk-position |
Position of asterisk |
'left' | 'right' |
left |
| show-message |
Whether to show the error message |
boolean |
true |
| inline-message |
Whether to display the error message inline |
boolean |
false |
| status-icon |
Whether to display an icon indicating validation result |
boolean |
false |
| validate-on-rule-change |
Whether to trigger validation when rules change |
boolean |
true |
| size |
Control the size of components |
'' | 'large' | 'default' | 'small' |
— |
| disabled |
Whether to disable all components |
boolean |
false |
| scroll-to-error |
When validation fails, scroll to the first error |
boolean |
false |
| scroll-into-view-options |
scrollIntoView options |
ScrollIntoViewOptions | boolean |
true |
Form Events
| Name |
Description |
Type |
| validate |
triggers after a form item is validated |
(prop: FormItemProp, isValid: boolean, message: string) => void |
Form Slots
| Name |
Description |
Subtags |
| default |
customize default content |
FormItem |
Form Exposes
| Name |
Description |
Type |
| validate |
Validate the whole form |
(callback?) => Promise<void> |
| validateField |
Validate specified fields |
(props?, callback?) => FormValidationResult |
| resetFields |
Reset specified fields |
(props?) => void |
| scrollToField |
Scroll to the specified fields |
(prop: FormItemProp) => void |
| clearValidate |
Clear validation messages |
(props?) => void |
| fields |
Get all fields context |
FormItemContext[] |
| getField |
Get a field context |
(prop: FormItemProp) => FormItemContext | undefined |
| setInitialValues |
Set initial values for form fields |
(initModel: Record<string, any>) => void |
FormItem Attributes
| Name |
Description |
Type |
Default |
| prop |
A key of model |
string | string[] |
— |
| label |
Label text |
string |
— |
| label-position |
Position of item label |
'left' | 'right' | 'top' |
'' |
| label-width |
Width of label |
string | number |
— |
| required |
Whether the field is required |
boolean |
— |
| rules |
Validation rules |
Arrayable<FormItemRule> |
— |
| error |
Field error message |
string |
— |
| show-message |
Whether to show the error message |
boolean |
true |
| inline-message |
Inline style validate message |
boolean |
false |
| size |
Control the size of components |
'' | 'large' | 'default' | 'small' |
— |
| for |
Same as for in native label |
string |
— |
| validate-status |
Validation state |
'' | 'error' | 'validating' | 'success' |
— |
FormItemRule
| Name |
Description |
Type |
Default |
| required |
Whether field is required |
boolean |
— |
| message |
Error message |
string |
— |
| trigger |
How the validator is triggered |
'blur' | 'change' |
— |
| min |
Minimum length/value |
number |
— |
| max |
Maximum length/value |
number |
— |
| len |
Exact length |
number |
— |
| type |
Field type |
'string' | 'number' | 'boolean' | 'email' | ... |
'string' |
| pattern |
Regex pattern |
RegExp |
— |
| validator |
Custom validation function |
(rule, value, callback) => void |
— |
| enum |
Enum values |
any[] |
— |
| whitespace |
Treat whitespace as empty |
boolean |
— |
| transform |
Transform value before validation |
(value) => any |
— |
| fields |
Nested object validation |
Record<string, Rule> |
— |
FormItem Slots
| Name |
Description |
Type |
| default |
Content of Form Item |
— |
| label |
Custom content to display on label |
{ label: string } |
| error |
Custom content to display validation message |
{ error: string } |
FormItem Exposes
| Name |
Description |
Type |
| size |
Form item size |
ComputedRef<'' | 'large' | 'default' | 'small'> |
| validateMessage |
Validation message |
Ref<string> |
| validateState |
Validation state |
Ref<'' | 'error' | 'validating' | 'success'> |
| validate |
Validate form item |
(trigger, callback?) => FormValidationResult |
| resetField |
Reset current field |
() => void |
| clearValidate |
Remove validation status |
() => void |
| setInitialValue |
Set initial value |
(value: any) => void |
Usage Examples
Basic Form
<template>
<el-form :model="form" label-width="120px">
<el-form-item label="Activity name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Activity zone">
<el-select v-model="form.region" placeholder="Select">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="Activity time">
<el-col :span="11">
<el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%" />
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-time-picker v-model="form.date2" placeholder="Pick a time" style="width: 100%" />
</el-col>
</el-form-item>
<el-form-item label="Instant delivery">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="Activity type">
<el-checkbox-group v-model="form.type">
<el-checkbox label="Online activities" name="type" />
<el-checkbox label="Promotion activities" name="type" />
</el-checkbox-group>
</el-form-item>
<el-form-item label="Resources">
<el-radio-group v-model="form.resource">
<el-radio label="Sponsor" />
<el-radio label="Venue" />
</el-radio-group>
</el-form-item>
<el-form-item label="Activity form">
<el-input v-model="form.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Create</el-button>
<el-button>Cancel</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { reactive } from 'vue'
const form = reactive({
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: '',
})
const => {
console.log('submit!', form)
}
</script>
Form Validation
<template>
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<el-form-item label="Name" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Email" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="Age" prop="age">
<el-input v-model.number="form.age" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">Submit</el-button>
<el-button @click="resetForm">Reset</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
const formRef = ref()
const form = reactive({
name: '',
email: '',
age: ''
})
const rules = reactive({
name: [
{ required: true, message: 'Please input name', trigger: 'blur' },
{ min: 3, max: 10, message: 'Length 3-10 characters', trigger: 'blur' }
],
email: [
{ required: true, message: 'Please input email', trigger: 'blur' },
{ type: 'email', message: 'Please input valid email', trigger: 'blur' }
],
age: [
{ required: true, message: 'Please input age', trigger: 'blur' },
{ type: 'number', message: 'Age must be a number', trigger: 'blur' }
]
})
const submitForm = async () => {
try {
await formRef.value.validate()
console.log('Form submitted:', form)
} catch (error) {
console.log('Validation failed')
}
}
const resetForm = () => {
formRef.value.resetFields()
}
</script>
Custom Validation
<template>
<el-form :model="form" :rules="rules" ref="formRef">
<el-form-item label="Password" prop="pass">
<el-input v-model="form.pass" type="password" />
</el-form-item>
<el-form-item label="Confirm" prop="checkPass">
<el-input v-model="form.checkPass" type="password" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">Submit</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
const formRef = ref()
const validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('Please input the password'))
} else {
if (form.checkPass !== '') {
formRef.value?.validateField('checkPass')
}
callback()
}
}
const validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('Please input the password again'))
} else if (value !== form.pass) {
callback(new Error("Two inputs don't match!"))
} else {
callback()
}
}
const form = reactive({
pass: '',
checkPass: ''
})
const rules = reactive({
pass: [{ validator: validatePass, trigger: 'blur' }],
checkPass: [{ validator: validatePass2, trigger: 'blur' }]
})
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
console.log('submit!')
} else {
console.log('error submit!')
}
})
}
</script>
Inline Form
<template>
<el-form :inline="true" :model="form">
<el-form-item label="Approved by">
<el-input v-model="form.user" placeholder="Approved by" />
</el-form-item>
<el-form-item label="Activity zone">
<el-select v-model="form.region" placeholder="Activity zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Query</el-button>
</el-form-item>
</el-form>
</template>
Dynamic Form Items
<template>
<el-form :model="form" ref="formRef">
<el-form-item
v-for="(item, index) in form.items"
:key="index"
:label="'Item ' + (index + 1)"
:prop="'items.' + index + '.value'"
:rules="{
required: true,
message: 'Item cannot be null',
trigger: 'blur'
}"
>
<el-input v-model="item.value" />
<el-button @click="removeItem(index)">Delete</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addItem">Add Item</el-button>
<el-button type="primary" @click="submitForm">Submit</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
const formRef = ref()
const form = reactive({
items: [{ value: '' }]
})
const addItem = () => {
form.items.push({ value: '' })
}
const removeItem = (index) => {
if (form.items.length > 1) {
form.items.splice(index, 1)
}
}
const submitForm = async () => {
try {
await formRef.value.validate()
console.log('Form submitted:', form)
} catch (error) {
console.log('Validation failed')
}
}
</script>
Label Alignment
<template>
<el-radio-group v-model="labelPosition" class="mb-4">
<el-radio-button value="left">Left</el-radio-button>
<el-radio-button value="right">Right</el-radio-button>
<el-radio-button value="top">Top</el-radio-button>
</el-radio-group>
<el-form :model="form" :label-position="labelPosition" label-width="auto">
<el-form-item label="Name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Activity zone">
<el-input v-model="form.region" />
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
const labelPosition = ref('right')
const form = reactive({
name: '',
region: ''
})
</script>
Common Issues
1. Form Submit on Enter
To prevent form submission on Enter key:
<el-form @submit.prevent>
<!-- form items -->
</el-form>
2. Nested Form Items
When nesting form items, set label-width on the nested item:
<el-form-item label="Outer">
<el-form-item label="Inner" label-width="100px">
<el-input />
</el-form-item>
</el-form-item>
3. Number Validation
Use .number modifier for number validation:
<el-input v-model.number="form.age" />
4. Validation Not Triggering
Ensure prop matches the model key exactly:
<el-form :model="form">
<el-form-item prop="name"> <!-- Must match form.name -->
<el-input v-model="form.name" />
</el-form-item>
</el-form>
Component Interactions
With Dialog
<template>
<el-button @click="dialogVisible = true">Open Form</el-button>
<el-dialog v-model="dialogVisible" title="Form Dialog">
<el-form :model="form" :rules="rules" ref="formRef">
<!-- form items -->
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="handleSubmit">Submit</el-button>
</template>
</el-dialog>
</template>
With API Submission
<script setup>
const handleSubmit = async () => {
try {
await formRef.value.validate()
loading.value = true
await api.submitForm(form)
ElMessage.success('Submitted successfully')
} catch (error) {
if (error !== 'validation') {
ElMessage.error('Submission failed')
}
} finally {
loading.value = false
}
}
</script>
Best Practices
- Use reactive for form data: Use
reactive() for form model
- Define rules separately: Keep validation rules in a separate reactive object
- Use prop attribute: Always set
prop on form items for validation
- Handle async validation: Use async/await for validation
- Reset after submit: Reset form after successful submission
- Disable during submit: Disable submit button during API calls