SwFormField

SwFormField is a layout container — it holds no input controls itself. Place input, select, or other native/custom controls in the default slot.

Two Modes

ModeTriggerError sourcescope slot field
Simplename not passederror prop directlyempty object {}
vee-validatename passed (requires outer useForm())auto from schema{ name, value, onInput, onChange, onBlur }

Use simple mode when the form is small and you manage validation by hand (e.g., on submit). Use vee-validate mode for forms with many fields, reusable schemas, or reactive error display.

Basic Usage (simple mode)

<SwFormField label="Username" required hint="3-20 characters">
  <input v-model="username" type="text" />
</SwFormField>

Basic Form Field

加载中...

Simple Mode + Error

Validate in your own code (e.g., on submit) and pass the error string to SwFormField via the error prop.

Simple mode + error prop

加载中...

vee-validate Mode

Pass name to enter vee-validate mode. The outer useForm({ validationSchema }) drives validation automatically. The default scope slot exposes a field object — v-bind it to the native input for two-way binding + auto-validation.

<script setup lang="ts">
import { useForm } from 'vee-validate'

const { handleSubmit, errors } = useForm({
  validationSchema: {
    username: (v: unknown) =>
      (typeof v === 'string' && v.length >= 3) || 'At least 3 characters',
  },
})

const submit = handleSubmit((values) => console.log(values))
</script>

<template>
  <form @submit.prevent="submit">
    <SwFormField v-slot="{ field, error }" name="username" label="Username">
      <input type="text" v-bind="field" :class="{ 'is-error': error }" />
    </SwFormField>
  </form>
</template>

framework pins vee-validate@4.x and uses functional schemas (no need for yup / zod / valibot). If you prefer a schema library, install it yourself and wrap with toTypedSchema().

vee-validate mode (functional schema)

加载中...

vee-validate mode: async submit + backend 422 error injection

加载中...

vee-validate mode: cross-field validation (signup, confirm password)

加载中...

vee-validate mode: async uniqueness check + hand-rolled debounce

加载中...

vee-validate mode: full schema (multiple field types + form meta + reset)

加载中...

Props

NameTypeRequiredDefaultDescription
namestring—vee-validate field name. Passed → vee-validate mode (error from schema); omitted → simple mode (use error prop)
labelstring—Field label text
requiredbooleanfalseShow a red * after the label (visual only, does not enforce validation)
hintstring—Helper text, auto-hidden when error is present
errorstring—Error message (shown directly in simple mode; in vee-validate mode acts as a fallback — shown only when the schema has no active error; avoid mixing with name). ⚠️ This is an error-message string (matching Element FormItem.error); the inner SwInput error is a boolean state — wire them as :error="!!field.error"

Slots

NamescopeDescription
default{ field, error }Input mount point. In vee-validate mode field is { name, value, onInput, onChange, onBlur } ready to v-bind; in simple mode it's an empty object {} (use v-model yourself). error is the current error string (empty when no error)

Usage Tips

  • Simple mode: small forms, ad-hoc validation, no schema library
  • vee-validate mode: >3 fields, reusable schema, reactive error display
  • Don't mix: within one <form>, don't pass both name and error — name takes precedence
  • For complex upload scenarios, use SwUploadImage / SwUploadVideo instead of native file input
  • Baseline state styles (framework#210): .error / .required default to --destructive, .hint to --muted-foreground, .label to weight 500 — states are distinguishable with zero config; override visual details with scoped project CSS

Style Tokens

These CSS variables can be overridden on your side (the fallback is the default):

VariableDefaultDescription
--sw-form-field-gap0.375remVertical gap between label / control / error message

Caveats

  • In simple mode field is an empty object, which makes the vee-validate upgrade easy to break (Rule SF-001) — without name, the slot's field is {}, so writing (or omitting) v-bind="field" raises no error. Later, when complexity grows and you add name to enable vee-validate, forgetting to add v-bind="field" or an explicit v-model means user input is lost entirely with no runtime warning (validation always passes). Once you pass name, one of the two bindings is mandatory.
  • Don't pass name and error together (Rule SF-002) — two error sources coexist: vee-validate's message wins when it has one, otherwise it falls back to the error prop, making it hard to tell which is showing. Standardize on one source: pure vee-validate with setFieldError() for server-side errors, or pure simple mode.
  • Show field-level errors inline, not in a toast (Rule UX-008) — that's exactly what this component exists for; validation errors in a transient toast disappear before they're read.