---
metaTitle: Forms Integration Guide | AwesCode UI
meta:
  - name: description
    content: Complete guide to building forms with AwesCode UI components - validation, error handling, and common patterns.
title: Forms Integration Guide
---

# Forms Integration Guide

Complete guide to building forms with AwesCode UI components.

## Table of Contents

- [Model-Based Forms (Recommended)](#model-based-forms-recommended)
- [Basic Form Structure](#basic-form-structure)
- [Form Inputs](#form-inputs)
- [Validation & Errors](#validation--errors)
- [File Uploads](#file-uploads)
- [Complex Forms](#complex-forms)
- [Modal Forms](#modal-forms)
- [Multi-Step Forms](#multi-step-forms)
- [Best Practices](#best-practices)

## Model-Based Forms (Recommended)

For CRUD operations and data editing, use `AwModelEdit` with Vue-MC models instead of raw `AwForm`. This provides automatic change detection, validation, error handling, and state management.

### Why Use AwModelEdit?

**AwModelEdit** wraps your form fields and provides:
- Automatic change detection with unsaved changes warning
- Save/Cancel buttons that appear when data changes
- Keyboard shortcuts (Ctrl+Enter to save, Escape to cancel)
- Automatic error scrolling to first invalid field
- Sticky action panel that follows scroll
- Integration with Vue-MC model lifecycle

**Use AwModelEdit when:**
- Editing database records (users, posts, settings, etc.)
- Need change detection and confirmation dialogs
- Working with complex nested data structures
- Need automatic state management
- Server-side validation with field-level errors

**Use AwForm when:**
- One-off operations (login, contact forms, search)
- Simple data submission without persistent state
- File uploads to specific endpoints
- Custom form handling logic needed

### Simple Edit Form

```markup
<template>
  <AwPage :title="title">
    <AwContentPlaceholder v-if="user.fetching" type="form" :lines="4" />

    <AwModelEdit v-else :model="user">
      <AwGrid>
        <AwCard title="Profile Information">
          <AwInput
            v-model="user.email"
            :error="user.errors.email"
            label="Email"
          />

          <AwInput
            v-model="user.first_name"
            :error="user.errors.first_name"
            label="First Name"
          />

          <AwInput
            v-model="user.last_name"
            :error="user.errors.last_name"
            label="Last Name"
          />
        </AwCard>
      </AwGrid>
    </AwModelEdit>
  </AwPage>
</template>

<script>
import User from '@/models/User'

export default {
  data() {
    return {
      user: new User({ id: this.$route.params.id })
    }
  },

  computed: {
    title() {
      return this.user.isNew() ? 'Create User' : `Edit ${this.user.first_name}`
    }
  },

  fetch() {
    // Load existing user data
    return this.user.isNew()
      ? Promise.resolve()
      : this.user.fetch()
  }
}
</script>
```

### Defining Vue-MC Models

```javascript
// models/User.js
import { BaseModel } from '@awes-io/vue-mc'

export default class User extends BaseModel {
  defaults() {
    return {
      id: null,
      email: '',
      first_name: '',
      last_name: '',
      status: 'active',
      role: {
        id: null,
        name: ''
      }
    }
  }

  routes() {
    return {
      fetch: '/api/users/{id}',
      save: '/api/users',           // POST for new
      update: '/api/users/{id}',    // PUT/PATCH for existing
      delete: '/api/users/{id}'     // DELETE
    }
  }

  validation() {
    return {
      email: 'required|email',
      first_name: 'required|min:2',
      last_name: 'required|min:2'
    }
  }

  // Computed properties
  get full_name() {
    return `${this.first_name} ${this.last_name}`.trim()
  }
}
```

### Nested Data Structures

```markup
<template>
  <AwModelEdit :model="company">
    <AwCard title="Company Details">
      <AwInput
        v-model="company.name"
        :error="company.errors.name"
        label="Company Name"
      />

      <AwInput
        v-model="company.tax_id"
        :error="company.errors.tax_id"
        label="Tax ID"
      />
    </AwCard>

    <AwCard title="Primary Contact">
      <AwInput
        v-model="company.contact.name"
        :error="company.errors['contact.name']"
        label="Contact Name"
      />

      <AwInput
        v-model="company.contact.email"
        :error="company.errors['contact.email']"
        label="Contact Email"
        type="email"
      />

      <AwTel
        v-model="company.contact.phone"
        :error="company.errors['contact.phone']"
        label="Phone"
      />
    </AwCard>

    <AwCard title="Address">
      <AwAddress
        v-model="company.address"
        :error="company.errors.address"
        :with-google-maps="true"
      />
    </AwCard>
  </AwModelEdit>
</template>

<script>
import Company from '@/models/Company'

export default {
  data() {
    return {
      company: new Company({ id: this.$route.params.id })
    }
  },

  fetch() {
    return this.company.isNew() ? Promise.resolve() : this.company.fetch()
  }
}
</script>
```

### Custom Save Actions

```markup
<template>
  <AwModelEdit
    :model="post"
    @saved="onSaved"
  >
    <AwInput v-model="post.title" :error="post.errors.title" label="Title" />
    <AwTextarea v-model="post.content" :error="post.errors.content" label="Content" />

    <template #after-buttons>
      <!-- Additional action buttons -->
      <AwButton @click="publishPost" theme="outline" color="success">
        Publish
      </AwButton>

      <AwButton @click="deletePost" theme="outline" color="error">
        Delete
      </AwButton>
    </template>
  </AwModelEdit>
</template>

<script>
export default {
  data() {
    return {
      post: new Post({ id: this.$route.params.id })
    }
  },

  methods: {
    onSaved(post) {
      this.$notify({ title: 'Post saved successfully!' })
      // Custom redirect
      if (post.isNew()) {
        this.$router.push(`/posts/${post.id}/edit`)
      }
    },

    async publishPost() {
      try {
        await this.post.save({ status: 'published' })
        this.$notify({ title: 'Post published!' })
      } catch (error) {
        this.$notify({
          type: 'error',
          title: 'Failed to publish post'
        })
      }
    },

    async deletePost() {
      if (confirm('Are you sure you want to delete this post?')) {
        await this.post.delete()
        this.$router.push('/posts')
      }
    }
  }
}
</script>
```

### Conditional Fields

```markup
<template>
  <AwModelEdit :model="user">
    <AwCard title="Account">
      <AwInput v-model="user.email" :error="user.errors.email" label="Email" />

      <!-- Show password field only for new users -->
      <template v-if="user.isNew()">
        <AwPassword
          v-model="user.password"
          :error="user.errors.password"
          label="Password"
          required
        />

        <AwPassword
          v-model="user.password_confirmation"
          :error="user.errors.password_confirmation"
          label="Confirm Password"
          required
        />
      </template>

      <!-- Show reset button for existing users -->
      <template v-else>
        <AwButton @click="sendPasswordReset" theme="outline">
          Send Password Reset Email
        </AwButton>
      </template>
    </AwCard>

    <AwCard title="Role & Status">
      <!-- Show different options based on user permissions -->
      <AwSelect
        v-if="$can('manage-roles')"
        v-model="user.role_id"
        :error="user.errors.role_id"
        :options="roles"
        label="Role"
      />

      <AwSwitcher
        v-model="user.is_active"
        :error="user.errors.is_active"
        label="Active Status"
      />
    </AwCard>
  </AwModelEdit>
</template>
```

### Relationship Management

```markup
<template>
  <AwModelEdit :model="role">
    <AwCard title="Role Information">
      <AwInput
        v-model="role.name"
        :error="role.errors.name"
        label="Role Name"
      />

      <AwTextarea
        v-model="role.description"
        :error="role.errors.description"
        label="Description"
      />
    </AwCard>

    <AwCard title="Permissions">
      <!-- Many-to-many relationship with checkboxes -->
      <AwCheckbox
        v-for="permission in permissions.models"
        :key="permission.id"
        v-model="role.permission_ids"
        :value="permission.id"
        :label="permission.name"
      >
        {{ permission.name }}
        <small class="text-muted">{{ permission.description }}</small>
      </AwCheckbox>
    </AwCard>
  </AwModelEdit>
</template>

<script>
import Role from '@/models/Role'
import Permissions from '@/collections/Permissions'

export default {
  data() {
    return {
      role: new Role({ id: this.$route.params.id }),
      permissions: new Permissions()
    }
  },

  async fetch() {
    // Load permissions first, then role
    await this.permissions.fetch()

    if (!this.role.isNew()) {
      await this.role.fetch()
    }
  }
}
</script>
```

### Advanced Configuration

```markup
<template>
  <AwModelEdit
    :model="settings"
    :saveText="$t('Save Changes')"
    :cancelText="$t('Reset')"
    :isNotify="true"
    :notifyText="$t('Settings updated successfully')"
    :isRedirect="false"
    :saveMethod="customSave"
    :alwaysVisible="true"
    saveErrorPath="response.data.message"
    @saved="onSettingsSaved"
    @error="onError"
  >
    <!-- Form fields -->
  </AwModelEdit>
</template>

<script>
export default {
  data() {
    return {
      settings: new Settings({ id: 1 })
    }
  },

  methods: {
    async customSave() {
      // Custom save logic
      return this.settings.save({
        validate: true,
        additional_data: this.getAdditionalData()
      })
    },

    onSettingsSaved(settings) {
      // Reload app config
      this.$store.dispatch('loadSettings')
    },

    onError(error) {
      console.error('Save failed:', error)
      this.$notify({
        type: 'error',
        title: 'Error',
        message: error.response?.data?.message || 'Failed to save settings'
      })
    }
  }
}
</script>
```

### Modal Forms with Models

```markup
<template>
  <div>
    <AwButton @click="openModal">Create User</AwButton>

    <AwModal :show="showModal" @close="closeModal">
      <template #title>
        {{ user.isNew() ? 'Create User' : 'Edit User' }}
      </template>

      <AwModelEdit
        :model="user"
        :isRedirect="false"
        @saved="onUserSaved"
      >
        <AwInput v-model="user.email" :error="user.errors.email" label="Email" />
        <AwInput v-model="user.first_name" :error="user.errors.first_name" label="First Name" />
        <AwInput v-model="user.last_name" :error="user.errors.last_name" label="Last Name" />
      </AwModelEdit>
    </AwModal>
  </div>
</template>

<script>
import User from '@/models/User'

export default {
  data() {
    return {
      showModal: false,
      user: null
    }
  },

  methods: {
    openModal(userId = null) {
      this.user = new User(userId ? { id: userId } : {})

      if (userId) {
        this.user.fetch().then(() => {
          this.showModal = true
        })
      } else {
        this.showModal = true
      }
    },

    closeModal() {
      this.showModal = false
      this.user = null
    },

    onUserSaved(user) {
      this.$notify({ title: 'User saved successfully!' })
      this.closeModal()
      // Refresh list or take other action
      this.$emit('user-saved', user)
    }
  }
}
</script>
```

## Basic Form Structure

> **Note:** For CRUD operations and data editing, use [AwModelEdit](#model-based-forms-recommended) instead. `AwForm` is best for one-off operations like login, search, and contact forms where you need raw HTTP form submission.

### Simple Login Form

```markup
<template>
  <AwForm
    url="/api/login"
    method="post"
    @sended="onSuccess"
    @error="onError"
  >
    <AwInput
      name="email"
      label="Email"
      type="email"
      required
    />

    <AwPassword
      name="password"
      label="Password"
      required
    />

    <AwButton type="submit" :loading="isLoading">
      Sign In
    </AwButton>
  </AwForm>
</template>

<script>
export default {
  data() {
    return {
      isLoading: false
    }
  },

  methods: {
    onSuccess(response) {
      this.$router.push('/dashboard')
    },

    onError(error) {
      console.error('Login failed:', error)
    }
  }
}
</script>
```

### Registration Form

```markup
<template>
  <AwForm url="/api/register" method="post" @sended="onRegistered">
    <AwInput name="name" label="Full Name" required />

    <AwInput name="email" label="Email" type="email" required />

    <AwPassword name="password" label="Password" required />

    <AwPassword name="password_confirmation" label="Confirm Password" required />

    <AwCheckbox name="terms" required>
      I agree to the Terms of Service
    </AwCheckbox>

    <AwButton type="submit" size="lg">
      Create Account
    </AwButton>
  </AwForm>
</template>
```

## Form Inputs

### Text Inputs

```markup
<AwForm url="/api/profile" method="patch">
  <!-- Basic text input -->
  <AwInput name="first_name" label="First Name" />

  <!-- With placeholder -->
  <AwInput
    name="company"
    label="Company"
    placeholder="Acme Inc."
  />

  <!-- With description -->
  <AwDescriptionInput
    name="username"
    label="Username"
    description="This will be your public identifier"
  />

  <!-- Textarea for long text -->
  <AwTextarea
    name="bio"
    label="Biography"
    rows="4"
  />
</AwForm>
```

### Select & Dropdowns

```markup
<AwForm url="/api/preferences" method="post">
  <!-- Simple select -->
  <AwSelect
    name="country"
    label="Country"
    :options="['USA', 'Canada', 'UK', 'Germany']"
  />

  <!-- Select with objects -->
  <AwSelectObject
    name="timezone"
    label="Timezone"
    :options="timezones"
    option-value="id"
    option-label="name"
  />

  <!-- Native select (for better mobile UX) -->
  <AwSelectNative
    name="language"
    label="Language"
    :options="[
      { value: 'en', text: 'English' },
      { value: 'es', text: 'Spanish' },
      { value: 'fr', text: 'French' }
    ]"
  />
</AwForm>
```

### Date & Time Inputs

```markup
<AwForm url="/api/events" method="post">
  <!-- Date picker -->
  <AwDate
    name="event_date"
    label="Event Date"
    :min="new Date()"
  />

  <!-- Birthday picker -->
  <AwBirthdayPicker
    name="birthday"
    label="Date of Birth"
  />

  <!-- Calendar view -->
  <AwCalendar
    v-model="selectedDate"
    name="appointment"
  />
</AwForm>
```

### Checkboxes & Radios

```markup
<AwForm url="/api/settings" method="patch">
  <!-- Single checkbox -->
  <AwCheckbox name="notifications">
    Enable email notifications
  </AwCheckbox>

  <!-- Radio buttons -->
  <div>
    <label>Subscription Plan</label>
    <AwRadio name="plan" value="free">Free</AwRadio>
    <AwRadio name="plan" value="pro">Pro</AwRadio>
    <AwRadio name="plan" value="enterprise">Enterprise</AwRadio>
  </div>

  <!-- Toggle switcher -->
  <AwSwitcher name="dark_mode" label="Dark Mode" />
</AwForm>
```

### Specialized Inputs

```markup
<AwForm url="/api/profile" method="patch">
  <!-- Telephone input -->
  <AwTel name="phone" label="Phone Number" />

  <!-- Money input -->
  <AwMoney name="budget" label="Budget" currency="USD" />

  <!-- Code input -->
  <AwCode name="verification_code" label="Verification Code" />

  <!-- Slider -->
  <AwSlider name="volume" label="Volume" :min="0" :max="100" />

  <!-- Chip select -->
  <AwChipSelect
    name="skills"
    label="Skills"
    :options="['Vue.js', 'React', 'Angular', 'Node.js']"
  />
</AwForm>
```

## Validation & Errors

### HTML5 Validation

```markup
<AwForm url="/api/submit" method="post">
  <!-- Required field -->
  <AwInput name="email" label="Email" required />

  <!-- Email validation -->
  <AwInput name="email" label="Email" type="email" required />

  <!-- Pattern validation -->
  <AwInput
    name="username"
    label="Username"
    pattern="[a-zA-Z0-9_]+"
    title="Only letters, numbers, and underscores"
  />

  <!-- Min/max length -->
  <AwInput
    name="password"
    label="Password"
    type="password"
    minlength="8"
    maxlength="100"
  />
</AwForm>
```

### Server-Side Error Handling

```markup
<template>
  <AwForm
    ref="form"
    url="/api/users"
    method="post"
    @error="handleErrors"
  >
    <AwInput name="email" label="Email" />
    <AwInput name="username" label="Username" />
    <AwButton type="submit">Submit</AwButton>
  </AwForm>
</template>

<script>
export default {
  methods: {
    handleErrors(error) {
      // AwForm automatically sets field errors via setCustomValidity
      // The errors are displayed on the respective input fields

      // Optionally handle specific error codes
      if (error.response?.status === 422) {
        console.log('Validation failed:', error.response.data.errors)
      }
    },

    // Programmatically set errors
    setCustomErrors() {
      this.$refs.form.setErrors({
        email: 'Email is already taken',
        username: 'Username must be unique'
      })
    },

    // Clear all errors
    clearErrors() {
      this.$refs.form.resetErrors()
    }
  }
}
</script>
```

### Custom Validation

```markup
<template>
  <AwForm url="/api/submit" @submit.prevent="validateAndSubmit">
    <AwInput
      ref="password"
      v-model="password"
      name="password"
      label="Password"
      type="password"
    />

    <AwInput
      ref="passwordConfirm"
      v-model="passwordConfirm"
      name="password_confirmation"
      label="Confirm Password"
      type="password"
    />

    <AwButton type="submit">Submit</AwButton>
  </AwForm>
</template>

<script>
export default {
  data() {
    return {
      password: '',
      passwordConfirm: ''
    }
  },

  methods: {
    validateAndSubmit(event) {
      // Clear previous errors
      this.$refs.password.$el.querySelector('input').setCustomValidity('')
      this.$refs.passwordConfirm.$el.querySelector('input').setCustomValidity('')

      // Custom validation
      if (this.password !== this.passwordConfirm) {
        this.$refs.passwordConfirm.$el
          .querySelector('input')
          .setCustomValidity('Passwords do not match')
        return
      }

      // Submit if valid
      event.target.submit()
    }
  }
}
</script>
```

## File Uploads

### Single File Upload

```markup
<AwForm url="/api/avatar" method="post" enctype="multipart/form-data">
  <AwUploader
    name="avatar"
    label="Profile Picture"
    accept="image/*"
    :max-size="5242880"
  />

  <AwButton type="submit">Upload</AwButton>
</AwForm>
```

### Multiple Files Upload

```markup
<AwForm url="/api/documents" method="post">
  <AwUploaderFiles
    name="documents"
    label="Documents"
    multiple
    :max-files="5"
    accept=".pdf,.doc,.docx"
  />

  <AwButton type="submit">Upload Documents</AwButton>
</AwForm>
```

### Image Upload with Cropper

```markup
<template>
  <AwForm url="/api/profile-image" method="post">
    <AwCropper
      v-model="croppedImage"
      name="profile_image"
      label="Profile Image"
      :aspect-ratio="1"
      :max-size="2097152"
    />

    <AwButton type="submit" :disabled="!croppedImage">
      Save Image
    </AwButton>
  </AwForm>
</template>
```

## Complex Forms

### Form with Nested Data

```markup
<template>
  <AwForm url="/api/companies" method="post" @sended="onSuccess">
    <!-- Company details -->
    <AwInput name="company[name]" label="Company Name" />
    <AwInput name="company[tax_id]" label="Tax ID" />

    <!-- Address -->
    <AwAddress
      name="company[address]"
      label="Company Address"
      :with-google-maps="true"
    />

    <!-- Contact person -->
    <AwInput name="contact[name]" label="Contact Name" />
    <AwInput name="contact[email]" label="Contact Email" type="email" />
    <AwTel name="contact[phone]" label="Contact Phone" />

    <AwButton type="submit">Create Company</AwButton>
  </AwForm>
</template>
```

### Dynamic Form Fields

```markup
<template>
  <AwForm url="/api/projects" method="post">
    <AwInput name="project_name" label="Project Name" />

    <!-- Dynamic team members -->
    <div v-for="(member, index) in members" :key="index">
      <h4>Team Member {{ index + 1 }}</h4>
      <AwInput :name="`members[${index}][name]`" label="Name" />
      <AwInput :name="`members[${index}][email]`" label="Email" type="email" />
      <AwButton @click="removeMember(index)" theme="outline" color="error">
        Remove
      </AwButton>
    </div>

    <AwButton @click="addMember" theme="outline">
      Add Team Member
    </AwButton>

    <AwButton type="submit">Create Project</AwButton>
  </AwForm>
</template>

<script>
export default {
  data() {
    return {
      members: [{ name: '', email: '' }]
    }
  },

  methods: {
    addMember() {
      this.members.push({ name: '', email: '' })
    },

    removeMember(index) {
      this.members.splice(index, 1)
    }
  }
}
</script>
```

## Modal Forms

### Simple Modal Form

```markup
<template>
  <AwModal :show="showModal" @close="showModal = false">
    <template #title>Create New User</template>

    <AwForm url="/api/users" method="post" @sended="onUserCreated">
      <AwInput name="name" label="Name" required />
      <AwInput name="email" label="Email" type="email" required />
      <AwSelect name="role" label="Role" :options="roles" />

      <template #buttons>
        <AwButton type="submit">Create User</AwButton>
        <AwButton @click="showModal = false" color="mono">
          Cancel
        </AwButton>
      </template>
    </AwForm>
  </AwModal>
</template>

<script>
export default {
  data() {
    return {
      showModal: false,
      roles: ['Admin', 'Editor', 'Viewer']
    }
  },

  methods: {
    onUserCreated(response) {
      this.showModal = false
      this.$notify({ title: 'User created successfully!' })
    }
  }
}
</script>
```

## Multi-Step Forms

### Multi-Step Registration

```markup
<template>
  <div>
    <AwProgress :value="(currentStep / totalSteps) * 100" />

    <AwForm
      ref="form"
      url="/api/register"
      method="post"
      @submit.prevent="handleStep"
    >
      <!-- Step 1: Basic Info -->
      <div v-show="currentStep === 1">
        <h2>Step 1: Basic Information</h2>
        <AwInput v-model="formData.name" name="name" label="Full Name" required />
        <AwInput v-model="formData.email" name="email" label="Email" type="email" required />
        <AwPassword v-model="formData.password" name="password" label="Password" required />
      </div>

      <!-- Step 2: Profile -->
      <div v-show="currentStep === 2">
        <h2>Step 2: Profile Details</h2>
        <AwBirthdayPicker v-model="formData.birthday" name="birthday" label="Birthday" />
        <AwTel v-model="formData.phone" name="phone" label="Phone" />
        <AwTextarea v-model="formData.bio" name="bio" label="Bio" />
      </div>

      <!-- Step 3: Preferences -->
      <div v-show="currentStep === 3">
        <h2>Step 3: Preferences</h2>
        <AwSelect v-model="formData.timezone" name="timezone" label="Timezone" :options="timezones" />
        <AwCheckbox v-model="formData.newsletter" name="newsletter">
          Subscribe to newsletter
        </AwCheckbox>
      </div>

      <!-- Navigation -->
      <AwFlow justify="between">
        <AwButton
          v-if="currentStep > 1"
          @click="currentStep--"
          theme="outline"
        >
          Previous
        </AwButton>

        <AwButton v-if="currentStep < totalSteps" type="button" @click="nextStep">
          Next
        </AwButton>

        <AwButton v-else type="submit">
          Complete Registration
        </AwButton>
      </AwFlow>
    </AwForm>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentStep: 1,
      totalSteps: 3,
      formData: {
        name: '',
        email: '',
        password: '',
        birthday: null,
        phone: '',
        bio: '',
        timezone: '',
        newsletter: false
      },
      timezones: ['UTC', 'EST', 'PST', 'GMT']
    }
  },

  methods: {
    nextStep() {
      // Validate current step before proceeding
      if (this.$refs.form.$el.checkValidity()) {
        this.currentStep++
      } else {
        this.$refs.form.$el.reportValidity()
      }
    },

    handleStep(event) {
      // Final submission
      event.target.submit()
    }
  }
}
</script>
```

## Best Practices

### 1. Choose the Right Form Component

```markup
<!-- Good: Use AwModelEdit for CRUD operations -->
<AwModelEdit :model="user">
  <AwInput v-model="user.email" :error="user.errors.email" label="Email" />
</AwModelEdit>

<!-- Good: Use AwForm for one-off operations -->
<AwForm url="/api/login" method="post">
  <AwInput name="email" label="Email" />
  <AwButton type="submit">Login</AwButton>
</AwForm>

<!-- Bad: Using AwForm for editing records (no change detection) -->
<AwForm url="/api/users/1" method="patch">
  <AwInput name="email" label="Email" />
</AwForm>
```

### 2. Always Use Labels

```markup
<!-- Good -->
<AwInput name="email" label="Email" />

<!-- Bad -->
<AwInput name="email" placeholder="Email" />
```

### 3. Provide Clear Feedback

```markup
<AwForm url="/api/save" @sended="onSuccess" @error="onError">
  <AwInput name="title" label="Title" />
  <AwButton type="submit" :loading="isSubmitting">
    {{ isSubmitting ? 'Saving...' : 'Save' }}
  </AwButton>
</AwForm>
```

### 4. Handle Loading States

For form data loading, use `AwContentPlaceholder`:

```markup
<template>
  <AwContentPlaceholder v-if="loading" type="form" :lines="4" />

  <AwForm v-else ref="form" url="/api/submit" @submit="onSubmit">
    <AwInput name="data" label="Data" />
    <AwButton type="submit">Submit</AwButton>
  </AwForm>
</template>

<script>
export default {
  data() {
    return {
      loading: true
    }
  },
  async mounted() {
    await this.loadFormData()
    this.loading = false
  }
}
</script>
```

For complex forms with multiple sections:

```markup
<template>
  <AwGrid v-if="loading" :col="{ lg: 3 }">
    <div span="{ lg: 2 }">
      <AwContentPlaceholder type="form" :lines="8" />
    </div>
    <div>
      <AwContentPlaceholder type="form" :lines="4" />
    </div>
  </AwGrid>

  <AwForm v-else url="/api/submit">
    <!-- Form content -->
  </AwForm>
</template>
```

### 5. Use Appropriate Input Types

```markup
<!-- Email validation -->
<AwInput name="email" label="Email" type="email" />

<!-- Number input -->
<AwInput name="age" label="Age" type="number" :min="0" :max="120" />

<!-- URL input -->
<AwInput name="website" label="Website" type="url" />
```

### 6. Group Related Fields

```markup
<AwForm url="/api/users" method="post">
  <AwCard title="Personal Information">
    <AwInput name="first_name" label="First Name" />
    <AwInput name="last_name" label="Last Name" />
    <AwBirthdayPicker name="birthday" label="Birthday" />
  </AwCard>

  <AwCard title="Contact Information">
    <AwInput name="email" label="Email" type="email" />
    <AwTel name="phone" label="Phone" />
  </AwCard>

  <AwButton type="submit">Save</AwButton>
</AwForm>
```

### 7. Provide Help Text

```markup
<AwDescriptionInput
  name="api_key"
  label="API Key"
  description="You can find your API key in the developer settings"
  type="password"
/>
```

### 8. Use Method Override for PUT/PATCH

```markup
<!-- For Laravel/REST APIs that don't support PUT/PATCH natively -->
<AwForm url="/api/users/1" method="patch">
  <AwInput name="name" label="Name" />
  <AwButton type="submit">Update</AwButton>
</AwForm>
```

## Common Patterns

### Search Form

```markup
<AwForm url="/api/search" method="get">
  <AwSearch
    name="q"
    placeholder="Search..."
    @input="debounceSearch"
  />
</AwForm>
```

### Filter Form

```markup
<AwForm url="/api/products" method="get">
  <AwSelect name="category" label="Category" :options="categories" />
  <AwSlider name="price_min" label="Min Price" :min="0" :max="1000" />
  <AwSlider name="price_max" label="Max Price" :min="0" :max="1000" />
  <AwButton type="submit">Apply Filters</AwButton>
  <AwButton type="reset" theme="outline">Reset</AwButton>
</AwForm>
```

### Inline Edit Form

```markup
<AwForm url="/api/tasks/1" method="patch" @sended="onSaved">
  <AwInput
    v-model="taskName"
    name="name"
    @blur="$refs.form.$el.submit()"
  />
</AwForm>
```

---

For more examples, see individual component documentation:
- [AwModelEdit](../components/organisms/aw-model-edit.md) - Model-based forms (recommended)
- [AwForm](../components/organisms/aw-form.md) - Raw form submission
- [AwInput](../components/atoms/aw-input.md) - Text inputs
- [AwSelect](../components/molecules/aw-select.md) - Select dropdowns
- [AwUploader](../components/organisms/aw-uploader.md) - File uploads
- [Vue-MC Models Guide](../../vue-mc/docs/models.md) - Model definitions
