---
title: "MotionForm"
description: "Fifteen animated form interactions built on TuffEx form controls."
category: Advanced
status: beta
since: 0.6.3
tags: [form, motion, validation, otp, files]
syncStatus: documented
verified: false
---

## Usage

### All Variants
`validate` only requests work: the caller writes back `status` and `error`, and changing `validationKey` replays the shake.
:::TuffDemoWrapper{demo="MotionFormDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import type { MotionFormStatus, MotionFormValue } from '@talex-touch/tuffex/motion-form'
  import { TxMotionForm } from '@talex-touch/tuffex/motion-form'
  import { ref } from 'vue'

  const email = ref('')
  const status = ref<MotionFormStatus>('default')
  const error = ref('')
  const validationKey = ref(0)
  function validate(value: MotionFormValue) {
    const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value))
    status.value = valid ? 'success' : 'error'
    error.value = valid ? '' : 'Enter a valid email.'
    validationKey.value++
  }
  </script>

  <template>
    <TxMotionForm
      v-model="email"
      variant="error-shake"
      label="Email"
      :status="status"
      :error="error"
      :validation-key="validationKey"
      @validate="validate"
    />
  </template>
---
:::

### Best Practices

- Keep values, validation messages, and business status in the caller; the component never decides success or clears an error on a timer.
- Treat `filesSelected` as a selection only: upload each `File` through your own service and drive `status` from the result.
- Always pass a `label` and localized `labels`; OTP digit names are built from `label` too.
- Use `readonly` only for text fields and OTP; use `disabled` for choices, files, the slider, and actions.
- Set `nativeType="submit"` only when an enclosing native form owns submission, and don't run the same work from both the click and the form's submit.

## API Reference

### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `MotionFormVariant` | `'floating-label-input'` | One of the fifteen effects in the variant table. |
| `modelValue` | `MotionFormValue` | unset | Controlled value; its shape depends on the variant. |
| `label` | `string` | `''` | Visible label and accessible name; falls back to `labels.field`. |
| `placeholder` | `string` | `''` | Text and select placeholder; a resting floating label covers it. |
| `description` | `string` | `''` | Helper text in the message region; error and status take precedence. |
| `size` | `'xs' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Height, choice mark, and radius; action `xs` maps to Button `sm`. |
| `disabled` | `boolean` | `false` | Blocks edits, selection, removal, password reveal, and action requests. |
| `readonly` | `boolean` | `false` | Makes text, textarea, and OTP read-only; password reveal still works. |
| `required` | `boolean` | `false` | Required marker and accessible state; native text fields also get `required`. |
| `status` | `'default' \| 'loading' \| 'success' \| 'error'` | `'default'` | Caller-driven status; `loading` blocks action requests. |
| `error` | `string` | `''` | When non-empty, sets the error state and shows this message. |
| `validationKey` | `string \| number` | unset | Replays the current error when it changes. |
| `labels` | `Partial<MotionFormLabels>` | unset | Localizes static text, digit names, and removal labels. |
| `options` | `MotionFormOption[]` | `[]` | Choices for radio, dropdown, and chips. |
| `inputType` | `'text' \| 'email' \| 'number' \| 'date'` | `'text'` | Text control type; the password variant switches text/password itself. |
| `autocomplete` | `string` | unset | Native autocomplete; the first OTP box always uses `one-time-code`. |
| `passwordVisible` | `boolean` | unset | Controlled reveal state, paired with `update:passwordVisible`. |
| `rows` | `number` | `2` | Minimum textarea rows. |
| `maxRows` | `number` | `8` | Maximum auto-grow rows before scrolling; never below `rows`. |
| `maxLength` | `number` | unset | Length limit for text and textarea; OTP is fixed at 4 digits. |
| `accept` | `string` | `'*/*'` | File type filter for picking and dropping. |
| `multiple` | `boolean` | `true` | Multiple files; when `false`, a new file replaces the old one. |
| `maxFiles` | `number` | `10` | Maximum number of files. |
| `min` | `number` | `0` | Slider minimum. |
| `max` | `number` | `100` | Slider maximum. |
| `step` | `number` | `1` | Slider step. |
| `formatValue` | `(value: number) => string` | unset | Formats the slider value and tooltip. |
| `nativeType` | `'button' \| 'submit'` | `'button'` | Native type of the submit button. |
| `motion` | `boolean` | `true` | Enables motion, still gated by visibility, KeepAlive, and reduced motion. |

### Events

| Event | Payload | Meaning |
| --- | --- | --- |
| `update:modelValue` | `MotionFormValue` | Fires on a user edit or selection. |
| `change` | `MotionFormValue` | Fires together with `update:modelValue`. |
| `update:passwordVisible` | `boolean` | Fires when password reveal toggles. |
| `focus`, `blur` | `FocusEvent` | Fire when focus enters or leaves the component, not on internal moves. |
| `search` | `string` | Fires when the search-expand text changes; sends no request. |
| `validate` | `MotionFormValue` | Asks the caller to validate the current value. |
| `submit` | `[value: MotionFormValue \| undefined, event: MouseEvent]` | Asks the caller to do the work; doesn't change `status`. |
| `otpComplete` | `string` | Fires when all four boxes hold digits; can fire again after an edit. |
| `filesSelected` | `FileUploaderFile[]` | Newly accepted files only; neither the full list nor an upload completion. |
| `fileRemove` | `{ id: string, value: FileUploaderFile[] }` | Fires on removal with the file ID and the new list. |

### Slots

| Slot | Scope | Use |
| --- | --- | --- |
| `label` | none | Visible label, linked to the control through a stable ID. |
| `prefix`, `suffix` | none | Input affixes; `suffix` replaces the password reveal button. |
| `option` | `{ option, selected }` | Radio and dropdown option content. |
| `chip` | `{ option }` | Chip label; the remove button stays. |
| `file` | `{ file, remove: () => void }` | File row content; the remove button stays. |
| `submit` | `{ status: MotionFormStatus, label: string }` | Submit button content, beside the state glyph. |
| `status` | `{ status, error, value }` | Content of the live message region. |

### Exposed Methods

| Method | Behavior |
| --- | --- |
| `focus()` | Focuses the first available input, textarea, or action button. |
| `blur()` | Blurs the focused descendant. |

### Types

```ts
import type { FileUploaderFile } from '@talex-touch/tuffex/file-uploader'
import type { MotionFormProps, MotionFormVariant } from '@talex-touch/tuffex/motion-form'

type MotionFormValue = string | number | boolean | (string | number)[] | FileUploaderFile[]
type MotionFormOption = TxSelectOption // { value, label, disabled?, icon?, description? }
// FileUploaderFile: { id, name, size, type, file: File }
// MOTION_FORM_VARIANTS exports the full readonly variant tuple.
```

#### MotionFormVariant

| Original ID | `variant` | Model | Effect |
| --- | --- | --- | --- |
| `frm1` | `floating-label-input` | `string` or `number` | The label floats up and shrinks on focus or with a value. |
| `frm2` | `input-focus-glow` | `string` or `number` | A halo spreads on focus and retracts on blur. |
| `frm3` | `password-toggle` | `string` | The reveal button toggles plain text and spins the eye; the value is untouched. |
| `frm4` | `search-expand` | `string` | Widens within the available space on focus; the icon shifts. |
| `frm5` | `checkbox-draw` | `boolean` | Checking draws the tick and pops the box. |
| `frm6` | `radio-scale` | `string` or `number` | Single choice; the inner dot springs in. |
| `frm7` | `error-shake` | `string` or `number` | Shakes when `error`, the error status, or `validationKey` changes. |
| `frm8` | `success-check` | `string` or `number` | Requests validation; success springs in a drawn check. |
| `frm9` | `select-dropdown` | `string` or `number` | Select opens with origin-aware scale, offset, and fade. |
| `frm10` | `multi-select-chips` | `(string \| number)[]` | Chips spring in and out; remove buttons update the array. |
| `frm11` | `textarea-auto-grow` | `string` | Grows with its scroll height, springy and capped. |
| `frm12` | `otp-input` | `string` | Four digit boxes auto-advance and distribute pasted text. |
| `frm13` | `file-upload-dropzone` | `FileUploaderFile[]` | Pick or drop files; dragging pulses the ring and lifts the arrow. |
| `frm14` | `range-slider` | `number` | Slider handles pointer and keyboard, with a springy thumb and value tooltip. |
| `frm15` | `form-submit-button` | optional `MotionFormValue` | Morphs text, spinner, and check from the caller's status. |

#### MotionFormLabels

Defaults are exported as `MOTION_FORM_DEFAULT_LABELS`; `labels` overrides any subset.

| Key | Default/type |
| --- | --- |
| `field` | `'Field'` |
| `showPassword`, `hidePassword`, `capsLock` | `'Show password'`, `'Hide password'`, `'CapsLock is on'` |
| `validate`, `validating`, `verified`, `validationError` | `'Validate'`, `'Validating…'`, `'Verified'`, `'Validation failed'` |
| `submit`, `submitting`, `submitted`, `submitError` | `'Submit'`, `'Submitting…'`, `'Submitted'`, `'Submission failed'` |
| `chooseFiles`, `dropFiles`, `fileHint` | `'Choose files'`, `'Drop files here'`, `'or click to browse'` |
| `searchOptions`, `noOptions` | `'Search options'`, `'No options'` |
| `otpDigit` | `(index: number) => string`; one-based default `Digit N of 4` |
| `removeOption` | `(label: string) => string`; default `Remove <label>` |
| `removeFile` | `(name: string) => string`; default `Remove <name>` |

### CSS Variables

| Variable | `md` default | Role |
| --- | --- | --- |
| `--tx-mf-height` | `36px` | Input and action height; sizes set 26/30/36/42px. |
| `--tx-mf-choice` | `22px` | Checkbox and radio mark size; sizes set 16/18/22/24px. |
| `--tx-mf-radius` | `12px` | Field radius; sizes set 8/10/12/12px. |

Colors come from `--tx-color-*`, `--tx-text-color-*`, `--tx-bg-color`, and `--tx-border-color-*`; motion variables are generated internally and aren't public API.

## Overview

- Input, Select, Checkbox, Radio, Textarea, FileUploader, and Slider own editing, options, drag and drop, and keyboard semantics; this component adds motion and controlled events.
- Labels sit beside their controls, and `useId` links labels, helper text, and OTP boxes. The password button is a native button with `aria-pressed`.
- Status and errors live in a `role="status" aria-live="polite"` region; replaying motion never resets focus.
- OTP accepts paste, autofill, Left/Right, Home/End, Backspace, and Delete; a new external value replaces all four boxes.
- The chips variant adds one option at a time and never adds a selected option twice.
- Motion and listeners pause when hidden or KeepAlive-inactive; reduced motion turns off decorative motion only, never input handling or measurement.

## Technologies

- Text transitions use `TxTextMorph` and curves come from the shared liquid spring resolver; upstream's timed error clearing and fake submit success became caller-owned `error` and `status`.
- Upstream: `src/components/forms/AnimatedFormElement.tsx` at [Amicro commit 43c29ce](https://github.com/Subhan-code/Amicro--Micro-transitions-/tree/43c29ce9cdd16459e3eab4992381b8d35b38776a), MIT, Copyright (c) 2026 SYED  SUBHAN UDDIN.
- Source: `packages/tuffex/packages/components/src/motion-form/`.

<TuffDocSourceLink />
