Form Components Documentation
This documentation covers the comprehensive form components available in uni-app, including input fields, selection controls, validation, and form handling patterns.
Overview
Form components are essential for user interaction and data collection in applications. uni-app provides a rich set of form components that work consistently across all platforms.
Basic Form Components
Input Components
uni-easyinput
Enhanced input component with built-in validation and styling.
<template>
<view class="form-container">
<uni-easyinput
v-model="userInput"
placeholder="Enter text"
:clearable="true"
:maxlength="100"
@input="onInput"
@focus="onFocus"
@blur="onBlur"
/>
<!-- Password input -->
<uni-easyinput
v-model="password"
type="password"
placeholder="Enter password"
:passwordIcon="true"
/>
<!-- Number input -->
<uni-easyinput
v-model="amount"
type="number"
placeholder="Enter amount"
:precision="2"
/>
</view>
</template>
<script>
export default {
data() {
return {
userInput: '',
password: '',
amount: ''
}
},
methods: {
onInput(value) {
console.log('Input value:', value)
},
onFocus() {
console.log('Input focused')
},
onBlur() {
console.log('Input blurred')
}
}
}
</script>
textarea
Multi-line text input component.
<template>
<textarea
v-model="description"
placeholder="Enter description"
:maxlength="500"
:auto-height="true"
:show-confirm-bar="false"
@input="onTextareaInput"
/>
</template>
<script>
export default {
data() {
return {
description: ''
}
},
methods: {
onTextareaInput(e) {
this.description = e.detail.value
}
}
}
</script>
Selection Components
uni-data-select
Data-driven select component with search functionality.
<template>
<view class="select-container">
<!-- Basic select -->
<uni-data-select
v-model="selectedValue"
:localdata="options"
placeholder="Select option"
@change="onSelectionChange"
/>
<!-- Multi-select -->
<uni-data-select
v-model="selectedValues"
:localdata="options"
:multiple="true"
placeholder="Select multiple options"
/>
<!-- Searchable select -->
<uni-data-select
v-model="searchableValue"
:localdata="searchableOptions"
:filterable="true"
placeholder="Search and select"
/>
</view>
</template>
<script>
export default {
data() {
return {
selectedValue: '',
selectedValues: [],
searchableValue: '',
options: [
{ value: 'option1', text: 'Option 1' },
{ value: 'option2', text: 'Option 2' },
{ value: 'option3', text: 'Option 3' }
],
searchableOptions: [
{ value: 'apple', text: 'Apple' },
{ value: 'banana', text: 'Banana' },
{ value: 'cherry', text: 'Cherry' },
{ value: 'date', text: 'Date' },
{ value: 'elderberry', text: 'Elderberry' }
]
}
},
methods: {
onSelectionChange(value) {
console.log('Selected:', value)
}
}
}
</script>
radio-group & radio
Radio button group for single selection.
<template>
<radio-group @change="onRadioChange">
<label class="radio-item" v-for="item in radioOptions" :key="item.value">
<radio :value="item.value" :checked="selectedRadio === item.value" />
<text>{{ item.text }}</text>
</label>
</radio-group>
</template>
<script>
export default {
data() {
return {
selectedRadio: '',
radioOptions: [
{ value: 'male', text: 'Male' },
{ value: 'female', text: 'Female' },
{ value: 'other', text: 'Other' }
]
}
},
methods: {
onRadioChange(e) {
this.selectedRadio = e.detail.value
}
}
}
</script>
checkbox-group & checkbox
Checkbox group for multiple selections.
<template>
<checkbox-group @change="onCheckboxChange">
<label class="checkbox-item" v-for="item in checkboxOptions" :key="item.value">
<checkbox :value="item.value" :checked="selectedCheckboxes.includes(item.value)" />
<text>{{ item.text }}</text>
</label>
</checkbox-group>
</template>
<script>
export default {
data() {
return {
selectedCheckboxes: [],
checkboxOptions: [
{ value: 'reading', text: 'Reading' },
{ value: 'music', text: 'Music' },
{ value: 'sports', text: 'Sports' },
{ value: 'travel', text: 'Travel' }
]
}
},
methods: {
onCheckboxChange(e) {
this.selectedCheckboxes = e.detail.value
}
}
}
</script>
Date and Time Components
uni-datetime-picker
Comprehensive date and time picker component.
<template>
<view class="datetime-container">
<!-- Date picker -->
<uni-datetime-picker
v-model="selectedDate"
type="date"
:start="startDate"
:end="endDate"
@change="onDateChange"
/>
<!-- Time picker -->
<uni-datetime-picker
v-model="selectedTime"
type="time"
@change="onTimeChange"
/>
<!-- DateTime picker -->
<uni-datetime-picker
v-model="selectedDateTime"
type="datetime"
@change="onDateTimeChange"
/>
<!-- Date range picker -->
<uni-datetime-picker
v-model="dateRange"
type="daterange"
@change="onDateRangeChange"
/>
</view>
</template>
<script>
export default {
data() {
return {
selectedDate: '',
selectedTime: '',
selectedDateTime: '',
dateRange: [],
startDate: '2020-01-01',
endDate: '2030-12-31'
}
},
methods: {
onDateChange(date) {
console.log('Selected date:', date)
},
onTimeChange(time) {
console.log('Selected time:', time)
},
onDateTimeChange(datetime) {
console.log('Selected datetime:', datetime)
},
onDateRangeChange(range) {
console.log('Selected range:', range)
}
}
}
</script>
Form Validation
uni-forms
Comprehensive form validation component.
<template>
<uni-forms ref="form" :model="formData" :rules="rules" label-width="80px">
<uni-forms-item label="Username" name="username" required>
<uni-easyinput
v-model="formData.username"
placeholder="Enter username"
trim="both"
/>
</uni-forms-item>
<uni-forms-item label="Email" name="email" required>
<uni-easyinput
v-model="formData.email"
placeholder="Enter email"
type="email"
/>
</uni-forms-item>
<uni-forms-item label="Password" name="password" required>
<uni-easyinput
v-model="formData.password"
placeholder="Enter password"
type="password"
/>
</uni-forms-item>
<uni-forms-item label="Confirm" name="confirmPassword" required>
<uni-easyinput
v-model="formData.confirmPassword"
placeholder="Confirm password"
type="password"
/>
</uni-forms-item>
<uni-forms-item label="Age" name="age" required>
<uni-easyinput
v-model="formData.age"
placeholder="Enter age"
type="number"
/>
</uni-forms-item>
<uni-forms-item label="Phone" name="phone" required>
<uni-easyinput
v-model="formData.phone"
placeholder="Enter phone number"
type="number"
/>
</uni-forms-item>
<uni-forms-item label="Gender" name="gender" required>
<uni-data-select
v-model="formData.gender"
:localdata="genderOptions"
placeholder="Select gender"
/>
</uni-forms-item>
<uni-forms-item label="Interests" name="interests">
<checkbox-group @change="onInterestsChange">
<label v-for="interest in interestOptions" :key="interest.value">
<checkbox :value="interest.value" />
<text>{{ interest.text }}</text>
</label>
</checkbox-group>
</uni-forms-item>
<uni-forms-item label="Bio" name="bio">
<textarea
v-model="formData.bio"
placeholder="Tell us about yourself"
:maxlength="200"
/>
</uni-forms-item>
<view class="form-actions">
<button @click="submitForm" class="submit-btn">Submit</button>
<button @click="resetForm" class="reset-btn">Reset</button>
</view>
</uni-forms>
</template>
<script>
export default {
data() {
return {
formData: {
username: '',
email: '',
password: '',
confirmPassword: '',
age: '',
phone: '',
gender: '',
interests: [],
bio: ''
},
rules: {
username: {
rules: [
{ required: true, errorMessage: 'Username is required' },
{ minLength: 3, errorMessage: 'Username must be at least 3 characters' },
{ maxLength: 20, errorMessage: 'Username cannot exceed 20 characters' },
{ pattern: /^[a-zA-Z0-9_]+$/, errorMessage: 'Username can only contain letters, numbers, and underscores' }
]
},
email: {
rules: [
{ required: true, errorMessage: 'Email is required' },
{ format: 'email', errorMessage: 'Please enter a valid email address' }
]
},
password: {
rules: [
{ required: true, errorMessage: 'Password is required' },
{ minLength: 8, errorMessage: 'Password must be at least 8 characters' },
{ pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, errorMessage: 'Password must contain uppercase, lowercase, and number' }
]
},
confirmPassword: {
rules: [
{ required: true, errorMessage: 'Please confirm your password' },
{
validator: (rule, value, data, callback) => {
if (value !== data.password) {
callback('Passwords do not match')
}
return true
}
}
]
},
age: {
rules: [
{ required: true, errorMessage: 'Age is required' },
{ format: 'number', errorMessage: 'Age must be a number' },
{ minimum: 13, errorMessage: 'Must be at least 13 years old' },
{ maximum: 120, errorMessage: 'Age cannot exceed 120' }
]
},
phone: {
rules: [
{ required: true, errorMessage: 'Phone number is required' },
{ pattern: /^1[3-9]\d{9}$/, errorMessage: 'Please enter a valid phone number' }
]
},
gender: {
rules: [
{ required: true, errorMessage: 'Please select your gender' }
]
}
},
genderOptions: [
{ value: 'male', text: 'Male' },
{ value: 'female', text: 'Female' },
{ value: 'other', text: 'Other' }
],
interestOptions: [
{ value: 'technology', text: 'Technology' },
{ value: 'sports', text: 'Sports' },
{ value: 'music', text: 'Music' },
{ value: 'travel', text: 'Travel' },
{ value: 'reading', text: 'Reading' }
]
}
},
methods: {
async submitForm() {
try {
const valid = await this.$refs.form.validate()
if (valid) {
console.log('Form data:', this.formData)
// Submit form data
const response = await uni.request({
url: 'https://api.example.com/submit',
method: 'POST',
data: this.formData
})
uni.showToast({
title: 'Form submitted successfully',
icon: 'success'
})
}
} catch (error) {
console.error('Form validation failed:', error)
}
},
resetForm() {
this.$refs.form.clearValidate()
this.formData = {
username: '',
email: '',
password: '',
confirmPassword: '',
age: '',
phone: '',
gender: '',
interests: [],
bio: ''
}
},
onInterestsChange(e) {
this.formData.interests = e.detail.value
}
}
}
</script>
Advanced Form Patterns
Dynamic Form Fields
<template>
<uni-forms ref="dynamicForm" :model="dynamicFormData">
<view v-for="(field, index) in formFields" :key="field.id" class="dynamic-field">
<uni-forms-item :label="field.label" :name="field.name" :required="field.required">
<!-- Text input -->
<uni-easyinput
v-if="field.type === 'text'"
v-model="dynamicFormData[field.name]"
:placeholder="field.placeholder"
/>
<!-- Number input -->
<uni-easyinput
v-else-if="field.type === 'number'"
v-model="dynamicFormData[field.name]"
type="number"
:placeholder="field.placeholder"
/>
<!-- Select -->
<uni-data-select
v-else-if="field.type === 'select'"
v-model="dynamicFormData[field.name]"
:localdata="field.options"
:placeholder="field.placeholder"
/>
<!-- Date -->
<uni-datetime-picker
v-else-if="field.type === 'date'"
v-model="dynamicFormData[field.name]"
type="date"
/>
</uni-forms-item>
<button @click="removeField(index)" class="remove-field-btn">
Remove Field
</button>
</view>
<button @click="addField" class="add-field-btn">Add Field</button>
<button @click="submitDynamicForm" class="submit-btn">Submit</button>
</uni-forms>
</template>
<script>
export default {
data() {
return {
formFields: [
{
id: 1,
name: 'field1',
label: 'Field 1',
type: 'text',
placeholder: 'Enter text',
required: true
}
],
dynamicFormData: {},
fieldCounter: 1
}
},
methods: {
addField() {
this.fieldCounter++
const newField = {
id: this.fieldCounter,
name: `field${this.fieldCounter}`,
label: `Field ${this.fieldCounter}`,
type: 'text',
placeholder: 'Enter text',
required: false
}
this.formFields.push(newField)
this.$set(this.dynamicFormData, newField.name, '')
},
removeField(index) {
const field = this.formFields[index]
this.formFields.splice(index, 1)
this.$delete(this.dynamicFormData, field.name)
},
async submitDynamicForm() {
try {
const valid = await this.$refs.dynamicForm.validate()
if (valid) {
console.log('Dynamic form data:', this.dynamicFormData)
uni.showToast({
title: 'Form submitted',
icon: 'success'
})
}
} catch (error) {
console.error('Validation failed:', error)
}
}
}
}
</script>
File Upload Form
<template>
<uni-forms ref="uploadForm" :model="uploadFormData">
<uni-forms-item label="Title" name="title" required>
<uni-easyinput v-model="uploadFormData.title" placeholder="Enter title" />
</uni-forms-item>
<uni-forms-item label="Description" name="description">
<textarea v-model="uploadFormData.description" placeholder="Enter description" />
</uni-forms-item>
<uni-forms-item label="Images" name="images">
<view class="upload-container">
<view class="image-list">
<view v-for="(image, index) in uploadedImages" :key="index" class="image-item">
<image :src="image.url" mode="aspectFill" />
<button @click="removeImage(index)" class="remove-image-btn">×</button>
</view>
<button @click="chooseImages" class="add-image-btn">
<uni-icons type="plus" size="30" />
</button>
</view>
</view>
</uni-forms-item>
<uni-forms-item label="Documents" name="documents">
<view class="document-list">
<view v-for="(doc, index) in uploadedDocuments" :key="index" class="document-item">
<uni-icons type="document" size="20" />
<text>{{ doc.name }}</text>
<button @click="removeDocument(index)" class="remove-doc-btn">×</button>
</view>
<button @click="chooseDocuments" class="add-document-btn">
Choose Documents
</button>
</view>
</uni-forms-item>
<view class="upload-progress" v-if="uploadProgress > 0">
<progress :percent="uploadProgress" stroke-width="4" />
<text>{{ uploadProgress }}%</text>
</view>
<button @click="submitUploadForm" :disabled="isUploading" class="submit-btn">
{{ isUploading ? 'Uploading...' : 'Submit' }}
</button>
</uni-forms>
</template>
<script>
export default {
data() {
return {
uploadFormData: {
title: '',
description: '',
images: [],
documents: []
},
uploadedImages: [],
uploadedDocuments: [],
uploadProgress: 0,
isUploading: false
}
},
methods: {
async chooseImages() {
try {
const result = await uni.chooseImage({
count: 9 - this.uploadedImages.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera']
})
for (let tempFilePath of result.tempFilePaths) {
this.uploadedImages.push({
url: tempFilePath,
name: `image_${Date.now()}.jpg`
})
}
} catch (error) {
console.error('Choose images failed:', error)
}
},
async chooseDocuments() {
try {
// #ifdef APP-PLUS
const result = await uni.chooseFile({
count: 5,
type: 'file'
})
for (let file of result.tempFiles) {
this.uploadedDocuments.push({
url: file.path,
name: file.name,
size: file.size
})
}
// #endif
// #ifdef H5
const input = document.createElement('input')
input.type = 'file'
input.multiple = true
input.accept = '.pdf,.doc,.docx,.txt'
input.onchange = (e) => {
for (let file of e.target.files) {
this.uploadedDocuments.push({
file: file,
name: file.name,
size: file.size
})
}
}
input.click()
// #endif
} catch (error) {
console.error('Choose documents failed:', error)
}
},
removeImage(index) {
this.uploadedImages.splice(index, 1)
},
removeDocument(index) {
this.uploadedDocuments.splice(index, 1)
},
async submitUploadForm() {
try {
const valid = await this.$refs.uploadForm.validate()
if (!valid) return
this.isUploading = true
this.uploadProgress = 0
// Upload images
const imageUrls = []
for (let i = 0; i < this.uploadedImages.length; i++) {
const image = this.uploadedImages[i]
const uploadedUrl = await this.uploadFile(image.url, 'image')
imageUrls.push(uploadedUrl)
this.uploadProgress = Math.round(((i + 1) / (this.uploadedImages.length + this.uploadedDocuments.length)) * 50)
}
// Upload documents
const documentUrls = []
for (let i = 0; i < this.uploadedDocuments.length; i++) {
const doc = this.uploadedDocuments[i]
const uploadedUrl = await this.uploadFile(doc.url || doc.file, 'document')
documentUrls.push({
url: uploadedUrl,
name: doc.name
})
this.uploadProgress = Math.round((50 + ((i + 1) / this.uploadedDocuments.length) * 50))
}
// Submit form data
const formData = {
...this.uploadFormData,
images: imageUrls,
documents: documentUrls
}
await uni.request({
url: 'https://api.example.com/upload-form',
method: 'POST',
data: formData
})
uni.showToast({
title: 'Upload successful',
icon: 'success'
})
this.resetUploadForm()
} catch (error) {
uni.showToast({
title: 'Upload failed',
icon: 'error'
})
} finally {
this.isUploading = false
this.uploadProgress = 0
}
},
uploadFile(filePath, type) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://api.example.com/upload',
filePath: filePath,
name: 'file',
formData: { type },
success: (res) => {
const data = JSON.parse(res.data)
resolve(data.url)
},
fail: reject
})
})
},
resetUploadForm() {
this.uploadFormData = {
title: '',
description: '',
images: [],
documents: []
}
this.uploadedImages = []
this.uploadedDocuments = []
}
}
}
</script>
Best Practices
Form Performance Optimization
- Lazy Loading: Load form options dynamically
- Debounced Validation: Avoid excessive validation calls
- Virtual Scrolling: For large form lists
- Memory Management: Clean up form data properly
Accessibility
- Label Association: Proper label-input relationships
- Keyboard Navigation: Support tab navigation
- Screen Reader Support: ARIA attributes
- Error Announcements: Clear error messages
Cross-Platform Considerations
- Input Types: Different behavior across platforms
- Validation Timing: Platform-specific validation patterns
- File Upload: Platform-specific implementations
- Date Pickers: Native vs custom implementations
This comprehensive form components documentation provides developers with the knowledge and examples needed to create robust, user-friendly forms in uni-app applications.