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
| Mode | Trigger | Error source | scope slot field |
|---|---|---|---|
| Simple | name not passed | error prop directly | empty object {} |
| vee-validate | name 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.xand uses functional schemas (no need for yup / zod / valibot). If you prefer a schema library, install it yourself and wrap withtoTypedSchema().
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
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | — | vee-validate field name. Passed → vee-validate mode (error from schema); omitted → simple mode (use error prop) | |
label | string | — | Field label text | |
required | boolean | false | Show a red * after the label (visual only, does not enforce validation) | |
hint | string | — | Helper text, auto-hidden when error is present | |
error | string | — | 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
| Name | scope | Description |
|---|---|---|
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 bothnameanderror—nametakes precedence - For complex upload scenarios, use
SwUploadImage/SwUploadVideoinstead of native file input - Baseline state styles (framework#210):
.error/.requireddefault to--destructive,.hintto--muted-foreground,.labelto 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):
| Variable | Default | Description |
|---|---|---|
--sw-form-field-gap | 0.375rem | Vertical gap between label / control / error message |
Caveats
- In simple mode
fieldis an empty object, which makes the vee-validate upgrade easy to break (Rule SF-001) — withoutname, the slot'sfieldis{}, so writing (or omitting)v-bind="field"raises no error. Later, when complexity grows and you addnameto enable vee-validate, forgetting to addv-bind="field"or an explicitv-modelmeans user input is lost entirely with no runtime warning (validation always passes). Once you passname, one of the two bindings is mandatory. - Don't pass
nameanderrortogether (Rule SF-002) — two error sources coexist: vee-validate's message wins when it has one, otherwise it falls back to theerrorprop, making it hard to tell which is showing. Standardize on one source: pure vee-validate withsetFieldError()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.