Guides
Guides Form Patterns

Form Patterns

Standard form styling patterns for Nuxt UI 3 with Zod validation, icons, and consistent design

CustomizationBeginner

Prerequisites:

getting-started
FormsValidationZodNuxt Ui

Overview

All forms in UXLab follow consistent patterns using Nuxt UI 3 components with Zod validation schemas. This guide documents the standard conventions for icons, sizing, spacing, and validation.

Icon Conventions

Use these Tabler icons consistently across forms:

Field TypeIconUsage
Emaili-tabler-mailEmail inputs, recipient fields
Passwordi-tabler-lockPassword and confirm password fields
Searchi-tabler-searchSearch inputs, filter fields
User/Namei-tabler-userName, username, profile fields
Title/Texti-tabler-bulbTitle fields, idea capture
Calendari-tabler-calendarDate/datetime inputs

Example

<UInput
  v-model="form.email"
  type="email"
  leading-icon="i-tabler-mail"
  placeholder="your@email.com"
/>

Sizing Conventions

Choose component size based on context:

SizeUsageExample
lgFull-page forms, primary actionsLogin, register, compose
mdModal dialogs, secondary formsSettings, confirmations
smFilters, search, dense listsIndex page filters, toolbars

Example

<!-- Full-page login form -->
<UInput
  v-model="form.email"
  size="lg"
  leading-icon="i-tabler-mail"
/>

<!-- Modal form -->
<UInput
  v-model="form.name"
  size="md"
  leading-icon="i-tabler-user"
/>

<!-- Filter input -->
<UInput
  v-model="searchQuery"
  size="sm"
  icon="i-tabler-search"
  placeholder="Search..."
/>

Spacing Conventions

Maintain consistent spacing between form elements:

SpacingUsageClass
Between fieldsStandard vertical spacing within a formspace-y-5
Between sectionsLarger spacing between logical groupsspace-y-8 or mb-8

Example

<UForm :schema="loginSchema" :state="form">
  <!-- space-y-5 between fields -->
  <div class="space-y-5">
    <UFormField name="email" label="Email">
      <UInput v-model="form.email" />
    </UFormField>

    <UFormField name="password" label="Password">
      <UInput v-model="form.password" type="password" />
    </UFormField>
  </div>

  <!-- Section break -->
  <div class="mt-8">
    <UButton type="submit" label="Sign In" />
  </div>
</UForm>

Validation Patterns

Basic Schema

Create reusable validation schemas in shared/schemas/:

// shared/schemas/common.ts
import { z } from 'zod'

export const emailSchema = z.string().email('Invalid email address')
export const passwordSchema = z.string().min(8, 'Password must be at least 8 characters')
export const requiredStringSchema = z.string().min(1, 'This field is required')
export const optionalStringSchema = z.string().optional()

Form Schema

Compose schemas for specific forms:

// shared/schemas/auth.ts
import { z } from 'zod'
import { emailSchema, passwordSchema } from './common'

export const loginSchema = z.object({
  email: emailSchema,
  password: passwordSchema,
})

export type LoginForm = z.infer<typeof loginSchema>

Component Usage

Use UForm with schema validation:

<script setup lang="ts">
import { loginSchema } from '#shared/schemas/auth'

const form = ref({
  email: '',
  password: '',
})

async function handleSubmit() {
  // Form is validated before this runs
  console.log('Valid form data:', form.value)
}
</script>

<template>
  <UForm
    :schema="loginSchema"
    :state="form"
    @submit="handleSubmit"
  >
    <div class="space-y-5">
      <UFormField name="email" label="Email Address">
        <UInput
          v-model="form.email"
          type="email"
          leading-icon="i-tabler-mail"
          size="lg"
          placeholder="your@email.com"
        />
      </UFormField>

      <UFormField
        name="password"
        label="Password"
        help="At least 8 characters"
      >
        <UInput
          v-model="form.password"
          type="password"
          leading-icon="i-tabler-lock"
          size="lg"
        />
      </UFormField>
    </div>

    <div class="mt-8">
      <UButton
        type="submit"
        label="Sign In"
        size="lg"
        block
      />
    </div>
  </UForm>
</template>

Common Patterns

Password Visibility Toggle

<script setup lang="ts">
const showPassword = ref(false)
</script>

<template>
  <UFormField name="password" label="Password">
    <UInput
      v-model="form.password"
      :type="showPassword ? 'text' : 'password'"
      leading-icon="i-tabler-lock"
      size="lg"
    >
      <template #trailing>
        <UButton
          :icon="showPassword ? 'i-tabler-eye-off' : 'i-tabler-eye'"
          variant="ghost"
          @click="showPassword = !showPassword"
        />
      </template>
    </UInput>
  </UFormField>
</template>

Search Input

<script setup lang="ts">
const searchQuery = ref('')

const filtered = computed(() => {
  if (!searchQuery.value) return items.value
  const query = searchQuery.value.toLowerCase()
  return items.value.filter(item =>
    item.title.toLowerCase().includes(query) ||
    item.description?.toLowerCase().includes(query)
  )
})
</script>

<template>
  <UInput
    v-model="searchQuery"
    icon="i-tabler-search"
    size="sm"
    placeholder="Search..."
  />
</template>

Form Field with Description

<UFormField
  name="recipients"
  label="Recipients"
  description="Separate multiple emails with commas"
  :hint="recipientCount > 0 ? `${recipientCount} recipients` : undefined"
>
  <UInput
    v-model="form.recipients"
    type="text"
    leading-icon="i-tabler-mail"
    size="lg"
  />
</UFormField>

Best Practices

  1. Always use Zod schemas for form validation instead of manual validation
  2. Import from #shared/schemas to reuse common validation patterns
  3. Add descriptive help text for complex fields (password requirements, format hints)
  4. Use dynamic hints to show real-time feedback (character count, recipient count)
  5. Keep forms accessible with proper labels, descriptions, and error messages
  6. Match icon context to field purpose (don't use i-tabler-mail for non-email fields)
  7. Consistent sizing within a form (don't mix lg and sm in the same form)
  8. Space fields logically using space-y-5 for fields and space-y-8 for sections
  • a: Appearance
  • ?: Keyboard shortcuts