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 objectlabel
is the text that will be displayed above the input elementpath
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 Prop | HighRise Prop | Notes |
---|---|---|
id | id | Required in HighRise |
FormItem Props
ghl-ui Prop | HighRise Prop | Notes |
---|---|---|
tooltip slot | tooltip | tooltip 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
- Always provide an
id
prop for accessibility - Use
v-model:value
for form inputs - Use
:size="md"
for default form size - Implement proper form validation rules
- Use appropriate size variants for different contexts
- Use the
model
prop to bind the form data to the form item andpath
to the form model property - Add proper ARIA labels for accessibility
- Use the feedback prop to show validation feedback or hint text
- Use the validation-status prop to force a validation status
- Use descriptive labels and hints
- Maintain consistent spacing in forms
- Group related inputs using form layout components
- Use validation status and messages for better UX
- Implement proper error handling and feedback
- Consider using tooltips for additional information
- Use input groups for complex input patterns
Additional Notes
- The component automatically handles focus states
- Error states are managed through validation status
- Supports keyboard navigation
- Maintains consistent styling with other form components
- Handles disabled states consistently
- Integrates seamlessly with form validation
- Maintains accessibility standards
- Supports custom styling through CSS variables
- Provides clear visual feedback for all states
- Works well on both desktop and mobile devices
- Supports both controlled and uncontrolled modes
- Includes built-in validation support