Skip to content

Migrating from ghl-ui Form to HighRise Form

This guide will help you migrate from the ghl-ui Form to the new HighRise Form. The inner elements have individual migration guides, this guide will focus on the changes to the form component itself.

Component Implementation Changes

Import Changes

diff
- import { UIForm, UIFormItem } from '@gohighlevel/ghl-ui'
+ import { HLForm, HLFormItem } from '@platform-ui/highrise'

Basic Usage Changes

The form component will be defaulted to the lg size to match the ghl-ui form label size. The input elements will be defaulted to their md sizes.

  • model is the form model, this is the form data object
  • label is the text that will be displayed above the input element
  • path is the path to the value in the form model, this is used to validate the form
diff
- <UIForm
-   id="registration-form"
-   :model="formModel"
-   :rules="validationRules"
-   ref="formRef"
- >
-   <UIFormItem label="Name" path="path_name" name="name" required :show-feedback="true">
-     <UIInput v-model="formModel.name" />
-   </UIFormItem>
-
-   <UIFormItem label="Email" path="path_email" name="email"   required :show-feedback="true">
-     <UIInput v-model="formModel.email" />
-   </UIFormItem>
-
-   <UIFormItem>
-     <UIButton @click="submitForm">Submit</UIButton>
-   </UIFormItem>
- </UIForm>

+ <HLForm
+   id="registration-form"
+   :model="formModel"
+   :rules="validationRules"
+   ref="formRef"
+   :size="lg"
+ >
+   <HLFormItem
+     label="Name"
+     path="path_name"
+     name="name"
+     required
+   >
+     <HLInput v-model:model-value="formModel.name" :size="md" :validation-status="validationStatus.name" :validation-message="validationFeedback.name" :show-feedback="true"/>
+   </HLFormItem>
+
+   <HLFormItem
+     label="Email"
+     path="path_email"
+     name="email"
+     required
+   >
+     <HLInput v-model:model-value="formModel.email" :size="md" :show-feedback="true"/>
+   </HLFormItem>
+
+   <HLFormItem>
+     <HLButton @click="submitForm">Submit</HLButton>
+   </HLFormItem>
+ </HLForm>

Props Changes

Form Props

ghl-ui PropHighRise PropNotes
ididRequired in HighRise

FormItem Props

ghl-ui PropHighRise PropNotes
tooltip slottooltiptooltip is now passed as a prop of type FormItemTooltipProps instead of a slot

Examples

Form with all input types and form validation

vue
<script lang="ts" setup>
import { ref } from 'vue'
import {
  UIButton,
  UICheckbox,
  UIDynamicTags,
  UIInput,
  UIInputGroup,
  UIInputGroupLabel,
  UIInputNumber,
  UIInputOtp,
  UIInputPhone,
  UIRadio,
  UISelect,
  UISlider,
  UIForm,
  UIFormItem,
} from '@gohighlevel/ghl-ui'
import {
  HLButton,
  HLCheckbox,
  HLForm,
  HLFormItem,
  HLInput,
  HLInputGroup,
  HLInputGroupLabel,
  HLInputNumber,
  HLInputOtp,
  HLInputPhone,
  HLInputTag,
  HLRadio,
  HLRadioGroup,
  HLSelect,
  HLSpace,
} from '@platform-ui/highrise'

interface FormModel {
  name: string
  age: number | undefined
  tags: string[]
  phone: string
  phoneCountryCode: string
  country: string
  agreeToTerms: boolean
  preferredContact: string
  description: string
  otp: string
  websiteUrl: string
  sliderValue: number
}

const formRef = ref()
const formModel = ref<FormModel>({
  name: '',
  age: undefined,
  tags: [],
  phone: '',
  phoneCountryCode: 'US',
  country: '',
  agreeToTerms: false,
  preferredContact: 'email',
  description: '',
  otp: '',
  websiteUrl: '',
  sliderValue: 50,
})

const rules = {
  name: [{ required: true, message: 'Please input your name', trigger: 'blur' }],
  age: [
    { required: true, message: 'Please input your age', trigger: 'blur' },
    {
      validator: (_, value) => {
        if (value && (value < 0 || value > 150)) {
          return new Error('Age must be between 0 and 150')
        }
        return true
      },
      trigger: 'blur',
    },
  ],
  tags: [{ required: true, type: 'array', message: 'Please add at least one tag', trigger: 'change' }],
  phone: [{ required: true, message: 'Please input your phone number', trigger: 'blur' }],
  country: [{ required: true, message: 'Please select a country', trigger: 'change' }],
  agreeToTerms: [
    {
      validator: (_, value) => {
        if (!value) return new Error('You must agree to the terms')
        return true
      },
      trigger: 'change',
    },
  ],
  otp: [{ required: true, message: 'Please input OTP', trigger: 'blur' }],
  description: [{ required: true, message: 'Please input description', trigger: 'blur' }],
}

const countryOptions = [
  { label: 'United States', value: 'US' },
  { label: 'Canada', value: 'CA' },
  { label: 'United Kingdom', value: 'UK' },
  { label: 'Australia', value: 'AU' },
  { label: 'Germany', value: 'DE' },
]

const contactOptions = [
  { label: 'Email', value: 'email' },
  { label: 'Phone', value: 'phone' },
  { label: 'SMS', value: 'sms' },
]

const handleSubmit = () => {
  formRef.value?.validate((errors: any) => {
    if (!errors) {
      console.log('Form validated successfully', formModel.value)
    } else {
      console.log('Validation failed', errors)
    }
  })
}

const handleOtpComplete = (data: { otp: string; state: string }) => {
  formModel.value.otp = data.otp
}

const handleOtpChange = (value: string) => {
  formModel.value.otp = value
}

const handlePhoneValidation = (isValid: boolean) => {
  console.log('Phone number is valid:', isValid)
}

const handleSliderUpdate = (value: number) => {
  formModel.value.sliderValue = value
}

defineProps<{ size?: 'sm' | 'md' | 'lg'; disabled?: boolean }>()
</script>
vue
<template>
  <UIForm ref="formRef" :model="formModel" :rules="rules" id="complete-form">
    <!-- Text Input -->
    <UIFormItem label="Name" path="textInput">
      <UIInput v-model="formModel.textInput" placeholder="Enter your name" id="text-input" />
    </UIFormItem>

    <!-- Number Input -->
    <UIFormItem label="Age" path="numberInput">
      <UIInputNumber v-model:value="formModel.numberInput" placeholder="Enter your age" :min="0" :max="150" id="number-input" />
    </UIFormItem>

    <!-- Tags Input -->
    <UIFormItem label="Interests" path="tags">
      <UIDynamicTags v-model:value="formModel.tags" placeholder="Add interests" id="tags-input" />
    </UIFormItem>

    <!-- Phone Input -->
    <UIFormItem label="Phone" path="phone">
      <UIInputPhone
        v-model:value="formModel.phone"
        v-model:countryCode="formModel.phoneCountryCode"
        @isValid="handlePhoneValidation"
        id="phone-input"
      />
    </UIFormItem>

    <!-- Select -->
    <UIFormItem label="Country" path="country">
      <UISelect v-model:value="formModel.country" :options="countryOptions" placeholder="Select your country" id="country-select" />
    </UIFormItem>

    <!-- Checkbox -->
    <UIFormItem path="agreeToTerms">
      <UICheckbox v-model:checked="formModel.agreeToTerms" id="terms-checkbox"> I agree to the terms and conditions </UICheckbox>
    </UIFormItem>

    <!-- Radio Buttons -->
    <UIFormItem label="Preferred Contact Method" path="preferredContact">
      <UIRadio
        v-for="option in contactOptions"
        :key="option.value"
        :value="option.value"
        :checked="formModel.preferredContact === option.value"
        @update:checked="
          checked => {
            if (checked) formModel.preferredContact = option.value
          }
        "
        :id="'radio-' + option.value"
      >
        {{ option.label }}
      </UIRadio>
    </UIFormItem>

    <!-- Textarea -->
    <UIFormItem label="Description" path="description">
      <UIInput v-model="formModel.description" type="textarea" :rows="4" placeholder="Enter description" id="description-textarea" />
    </UIFormItem>

    <!-- OTP Input -->
    <UIFormItem label="OTP" path="otp">
      <UIInputOtp :fields="6" placeholder="0" @onComplete="handleOtpComplete" @onChange="handleOtpChange" id="otp-input" />
    </UIFormItem>

    <!-- Input Group -->
    <UIFormItem label="Website" path="websiteUrl">
      <UIInputGroup>
        <UIInputGroupLabel>https://</UIInputGroupLabel>
        <UIInput v-model="formModel.websiteUrl" placeholder="your-website" id="website-input" />
        <UIInputGroupLabel>.com</UIInputGroupLabel>
      </UIInputGroup>
    </UIFormItem>

    <!-- Slider -->
    <UIFormItem label="Progress" path="sliderValue">
      <UISlider v-model:value="formModel.sliderValue" :min="0" :max="100" :step="1" id="slider-input" />
    </UIFormItem>

    <!-- Submit Button -->
    <UIFormItem>
      <UIButton @click="handleSubmit" id="submit-button"> Submit </UIButton>
    </UIFormItem>
  </UIForm>
</template>
vue
<template>
  <HLForm
    ref="formRef"
    :model="formModel"
    :rules="rules"
    :size="size"
    :disabled="disabled"
    label-placement="left"
    require-mark-placement="right"
    style="max-width: 600px; margin: 0 auto; padding: 20px"
  >
    <!-- Name Input -->
    <HLFormItem label="Name" path="name">
      <HLInput id="name-input" v-model:model-value="formModel.name" placeholder="Enter your name" />
    </HLFormItem>

    <!-- Age Input -->
    <HLFormItem label="Age" path="age">
      <HLInputNumber id="age-input" v-model:value="formModel.age" placeholder="Enter your age" :min="0" :max="150" />
    </HLFormItem>

    <!-- Tags Input -->
    <HLFormItem label="Interests" path="tags">
      <HLInputTag id="interests-input" v-model:value="formModel.tags" placeholder="Add interests" />
    </HLFormItem>

    <!-- Phone Input -->
    <HLFormItem label="Phone" path="phone">
      <HLInputPhone
        id="phone-input"
        v-model:value="formModel.phone"
        v-model:country-code="formModel.phoneCountryCode"
        @is-valid="handlePhoneValidation"
      />
    </HLFormItem>

    <!-- Country Select -->
    <HLFormItem label="Country" path="country">
      <HLSelect id="country-select" v-model:value="formModel.country" :options="countryOptions" placeholder="Select your country" />
    </HLFormItem>

    <!-- Terms Checkbox -->
    <HLFormItem path="agreeToTerms">
      <HLCheckbox id="agree-to-terms-checkbox" v-model:checked="formModel.agreeToTerms"> I agree to the terms and conditions </HLCheckbox>
    </HLFormItem>

    <!-- Contact Method Radio -->
    <HLFormItem label="Preferred Contact Method" path="preferredContact">
      <HLRadioGroup id="preferred-contact-method-radio-group" v-model:value="formModel.preferredContact">
        <HLSpace>
          <HLRadio v-for="option in contactOptions" :id="`contact-method-${option.value}`" :key="option.value" :value="option.value">
            {{ option.label }}
          </HLRadio>
        </HLSpace>
      </HLRadioGroup>
    </HLFormItem>

    <!-- Description Textarea -->
    <HLFormItem label="Description" path="description">
      <HLInput
        id="description-input"
        v-model:model-value="formModel.description"
        type="textarea"
        :rows="4"
        placeholder="Enter description"
      />
    </HLFormItem>

    <!-- OTP Input -->
    <HLFormItem label="OTP" path="otp">
      <HLInputOtp id="otp-input" :fields="6" placeholder="0" @on-complete="handleOtpComplete" />
    </HLFormItem>

    <!-- Website URL -->
    <HLFormItem label="Website" path="websiteUrl">
      <HLInputGroup>
        <HLInputGroupLabel>https://</HLInputGroupLabel>
        <HLInput id="website-url-input" v-model:model-value="formModel.websiteUrl" placeholder="your-website" />
        <HLInputGroupLabel>.com</HLInputGroupLabel>
      </HLInputGroup>
    </HLFormItem>

    <!-- Submit Button -->
    <HLFormItem>
      <HLButton id="submit-button" @click="handleSubmit">Submit</HLButton>
    </HLFormItem>
  </HLForm>
</template>

FormItem with Tooltip

diff
- <script>
- const nameTooltip = {
-   text: () => h('b', {}, 'some bold text'),
- }
- </script>
- <template>
-   <UIForm ref="formRef" :model="formModel" :rules="rules" id="complete-form">
-     <!-- Text Input -->
-      <UIFormItem label="Name"  :tooltip="nameTooltip">
-       <UIInput id="valueRef" v-model="valueRef" placeholder="Input Name"  />
-     </UIFormItem>
-   </UIForm>
- </template>

+ <script>
+ const nameTooltip: FormItemTooltipProps = {
+ tooltipContent: 'some bold text',
+ placement: 'top',
// any other props you want to pass to the tooltip
 }
+ </script>
+ <template>
+  <HLForm id="tooltip-form" :model="model">
+   <HLFormItem label="Name"  :tooltip="nameTooltip">
+     <HLInput id="valueRef" v-model="valueRef" placeholder="Input Name"  />
+  </HLFormItem>
+ </HLForm>
+ </template>

Best Practices

  1. Always provide an id prop for accessibility
  2. Use v-model:value for form inputs
  3. Use :size="md" for default form size
  4. Implement proper form validation rules
  5. Use appropriate size variants for different contexts
  6. Use the model prop to bind the form data to the form item and path to the form model property
  7. Add proper ARIA labels for accessibility
  8. Use the feedback prop to show validation feedback or hint text
  9. Use the validation-status prop to force a validation status
  10. Use descriptive labels and hints
  11. Maintain consistent spacing in forms
  12. Group related inputs using form layout components
  13. Use validation status and messages for better UX
  14. Implement proper error handling and feedback
  15. Consider using tooltips for additional information
  16. Use input groups for complex input patterns

Additional Notes

  1. The component automatically handles focus states
  2. Error states are managed through validation status
  3. Supports keyboard navigation
  4. Maintains consistent styling with other form components
  5. Handles disabled states consistently
  6. Integrates seamlessly with form validation
  7. Maintains accessibility standards
  8. Supports custom styling through CSS variables
  9. Provides clear visual feedback for all states
  10. Works well on both desktop and mobile devices
  11. Supports both controlled and uncontrolled modes
  12. Includes built-in validation support