Skip to content

Form

Basic Usage

Building a form using UniForm and UniFormItem is incredibly straightforward.

Internally, the form utilizes async-validator to validate the data entered into it. You can set validation rules for each form item using the rules property. For more information regarding validation rules, please refer to the async-validator GitHub page.

View Source
vue
<script setup lang="ts">
import { UniForm, UniFormItem, UniInput, UniButton, UniPassword, UniSelect, UniSelectOption } from "unify-ui";
import { ref } from "vue";

const model = ref({
  username: "",
  password: "",
  address: "",
  gender: "",
});

const formRef = ref<InstanceType<typeof UniForm> | null>(null);

async function handleSubmit() {
  try {
    await formRef.value?.validate();
    console.log("validate pass, model:", model);
  } catch (error) {
    console.error("validate error");
  }
}

function handleReset() {
  formRef.value?.resetFields();
}
</script>

<template>
  <UniForm ref="formRef" v-model:model="model">
    <UniFormItem name="username" label="Username" :rules="[{ required: true, message: 'Username is required' }]">
      <UniInput v-model="model.username" />
    </UniFormItem>

    <UniFormItem name="password" label="Password" :rules="[{ required: true, message: 'Password is required' }]">
      <UniPassword v-model="model.password" />
    </UniFormItem>

    <UniFormItem name="address" label="Address" :rules="[{ required: true, message: 'Address is requried' }]">
      <UniInput v-model="model.address" />
    </UniFormItem>

    <UniFormItem name="gender" label="Geneder" :rules="[{ required: true, message: 'Gender is requried' }]">
      <UniSelect v-model="model.gender">
        <UniSelectOption label="male" value="male" />
        <UniSelectOption label="female" value="Female" />
      </UniSelect>
    </UniFormItem>

    <div>
      <UniButton class="button" type="soft" @click="handleSubmit">Submit</UniButton>
      <UniButton class="button" @click="handleReset">Reset</UniButton>
    </div>
  </UniForm>
</template>

<style scoped>
.button {
  width: 100px;
  margin-right: 20px;
}
</style>

Customized Form Control

In some cases, you might need to use a custom form control not provided by Unify UI. The appearance of this customized form control may behave unexpectedly when the value is invalid. For instance, you may want the custom form control to display a red border when the value is invalid. There are two methods to achieve this.

The first method involves using UniFormItem, which provides Scoped Slots with a validateStatus state. When the form control's value is invalid, validateStatus is set to error, and when the value is valid, it is set to success. You can use this to customize the appearance of the form control:

View Source
vue
<script setup lang="ts">
import { UniForm, UniFormItem, UniButton } from "unify-ui";
import { ref } from "vue";

const model = ref({
  username: "",
});

const formRef = ref<InstanceType<typeof UniForm> | null>(null);

async function handleSubmit() {
  try {
    await formRef.value?.validate();
    console.log("validate pass, model:", model);
  } catch (error) {
    console.error("validate error");
  }
}

function handleReset() {
  formRef.value?.resetFields();
}
</script>

<template>
    <UniForm ref="formRef" v-model:model="model">
      <UniFormItem
        name="username"
        :rules="[{ required: true, message: 'Username is required' }]"
        label="Username"
      >
        <template #default="slotProps">
          <input
            v-model="model.username"
            class="input"
            :class="{ 'input--error': slotProps.validateStatus === 'error' }"
          />
        </template>
      </UniFormItem>

      <div>
        <UniButton class="button" type="soft" @click="handleSubmit">Submit</UniButton>
        <UniButton class="button" @click="handleReset">Reset</UniButton>
      </div>
    </UniForm>
</template>

<style scoped>
.input {
  border: 1px solid black;
}

.input--error {
  border: 1px solid red;
}

.button {
  width: 100px;
  margin-right: 20px;
}
</style>

The second method involves using the composable function useGetFormItemValidateStatus, which provides access to the validateStatus state. It's important to mention that when using this method, you need to wrap the form control with UniFormItem because the validateStatus state is provided by UniFormItem using the provide API:

View Source

Source code of customized input:

vue
<script setup lang="ts">
import { useGetFormItemValidateStatus } from "unify-ui";
const validateStatus = useGetFormItemValidateStatus();
</script>

<template>
  <input v-bind="$attrs" class="input" :class="{ 'input--error': validateStatus === 'error' }" />
</template>

<style scoped>
.input {
  border: 1px solid black;
}

.input--error {
  border: 1px solid red;
}
</style>

Source code of form example:

vue
<script setup lang="ts">
import { UniForm, UniFormItem, UniButton } from "unify-ui";
import { ref } from "vue";

import CustomizedInput from "./ShowCaseCustomizedFormControl2Input.vue";

const model = ref({
  username: "",
});

const formRef = ref<InstanceType<typeof UniForm> | null>(null);

async function handleSubmit() {
  try {
    await formRef.value?.validate();
    console.log("validate pass, model:", model);
  } catch (error) {
    console.error("validate error");
  }
}

function handleReset() {
  formRef.value?.resetFields();
}
</script>

<template>
  <UniForm ref="formRef" v-model:model="model">
    <UniFormItem name="username" :rules="[{ required: true, message: 'Username is required' }]" label="Username">
      <CustomizedInput v-model="model.username" />
    </UniFormItem>

    <div>
      <UniButton class="button" type="soft" @click="handleSubmit">Submit</UniButton>
      <UniButton class="button" @click="handleReset">Reset</UniButton>
    </div>
  </UniForm>
</template>

<style scoped>
.button {
  width: 100px;
  margin-right: 20px;
}
</style>

Layout

The form layout can be set using the layout property. The default value is vertical.

View Source
vue
<script setup lang="ts">
import { UniForm, UniFormItem, UniInput, UniButton, UniPassword, UniRadioGroup } from "unify-ui";
import { ref } from "vue";

const model = ref({
  username: "",
  password: "",
  address: "",
});

const formRef = ref<InstanceType<typeof UniForm> | null>(null);

async function handleSubmit() {
  try {
    await formRef.value?.validate();
    console.log("validate pass, model:", model);
  } catch (error) {
    console.error("validate error");
  }
}

function handleReset() {
  formRef.value?.resetFields();
}

const options = ["horizontal", "vertical", "inline"].map((item) => ({ label: item, value: item }));
const layout = ref<InstanceType<typeof UniForm>["$props"]["layout"]>("horizontal");
</script>

<template>
  <div class="container">
    <UniRadioGroup
      v-model="layout"
      :options="options"
      name="layout-options"
      inline
      :style="{ marginBottom: '20px' }"
    ></UniRadioGroup>

    <UniForm ref="formRef" v-model:model="model" :layout="layout" label-width="90px">
      <UniFormItem
        name="username"
        :rules="[{ required: true, message: 'Username is required' }]"
        label="Username"
      >
        <UniInput v-model="model.username" class="input" />
      </UniFormItem>

      <UniFormItem
        name="password"
        :rules="[{ required: true, message: 'Password is required' }]"
        label="Password"
      >
        <UniPassword v-model="model.password" class="input" />
      </UniFormItem>

      <UniFormItem name="address" label="Address">
        <UniInput v-model="model.address" class="input" />
      </UniFormItem>

      <UniFormItem>
        <UniButton class="button" type="soft" @click="handleSubmit">Submit</UniButton>
        <UniButton class="button" @click="handleReset">Reset</UniButton>
      </UniFormItem>
    </UniForm>
  </div>
</template>

<style scoped>
.container {
  padding: 20px;
}

.button {
  width: 100px;
  margin-right: 20px;
}

.input {
  width: 150px;
}
</style>

Form API

Properties

ts
export interface FormProps {
  model: Model;
  layout?: "horizontal" | "vertical" | "inline";
  labelWidth?: string;
}

const props = withDefaults(defineProps<FormProps>(), {
  layout: "vertical",
});

Events

ts
const emits = defineEmits<{
  (e: "update:model", value: Model): void;
}>();

Slots

ts
defineSlots<{
  default(props: {}): any;
}>();

Expose

ts
defineExpose({
  validate, // trigger data entry validation
  clearValidate, // clear data entry validation,
  resetFields, // reset data and clear validation
});

FormItem API

Properties

ts
import type { RuleItem } from "async-validator";

const props = withDefaults(
  defineProps<{
    label?: string;
    name?: string;
    rules?: RuleItem[];
  }>(),
  {
    label: undefined,
    name: undefined,
    rules: () => [],
  }
);

Slots

ts
export type ValidateStatus = "success" | "error" | undefined;

defineSlots<{
  default(props: { validateStatus: ValidateStatus }): any;
}>();

Released under the MIT License.