Skip to content

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.

vue
<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.

vue
<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.

vue
<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.

vue
<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.

vue
<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.

vue
<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.

vue
<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

vue
<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

vue
<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

  1. Lazy Loading: Load form options dynamically
  2. Debounced Validation: Avoid excessive validation calls
  3. Virtual Scrolling: For large form lists
  4. Memory Management: Clean up form data properly

Accessibility

  1. Label Association: Proper label-input relationships
  2. Keyboard Navigation: Support tab navigation
  3. Screen Reader Support: ARIA attributes
  4. Error Announcements: Clear error messages

Cross-Platform Considerations

  1. Input Types: Different behavior across platforms
  2. Validation Timing: Platform-specific validation patterns
  3. File Upload: Platform-specific implementations
  4. 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.

一次开发,多端部署 - 让跨平台开发更简单