Text Input
Basic Usage
Basic text input with two-way binding and placeholder text.
<template>
<HLInput v-model:modelValue="value" placeholder="Enter text..." />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>All Sizes
Available size variants from largest (lg) to smallest (3xs).
<template>
<HLInput v-model:modelValue="value" size="lg" placeholder="Large input" />
<HLInput v-model:modelValue="value" size="md" placeholder="Medium input" />
<HLInput v-model:modelValue="value" size="sm" placeholder="Small input" />
<HLInput v-model:modelValue="value" size="xs" placeholder="Extra small input" />
<HLInput v-model:modelValue="value" size="2xs" placeholder="2x Extra small input" />
<HLInput v-model:modelValue="value" size="3xs" placeholder="3x Extra small input" />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Text Align
Control text alignment within the input field.
<template>
<HLInput v-model:modelValue="value" text-align="start" placeholder="Left aligned" />
<HLInput v-model:modelValue="value" text-align="center" placeholder="Center aligned" />
<HLInput v-model:modelValue="value" text-align="end" placeholder="Right aligned" />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Custom font styles
Override the input's font size and weight with custom values.
<template>
<HLInput v-model:modelValue="value" font-size="var(--hr-font-size-4xl)" font-weight="var(--hr-font-weight-semibold)" />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Prefix
Add content before the input text.
<template>
<HLInput v-model:modelValue="value">
<template #prefix>$</template>
</HLInput>
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Suffix
Add content after the input text.
<template>
<HLInput v-model:modelValue="value">
<template #suffix>$</template>
</HLInput>
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Disabled state
Input field in disabled state, preventing user interaction.
<template>
<HLInput v-model:modelValue="value" disabled />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Readonly state
Input field in readonly state, allowing only text selection.
<template>
<HLInput v-model:modelValue="value" readonly />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('sample text')
</script>Auto size
Input that automatically adjusts its width based on content.
<template>
<HLInput v-model:modelValue="value" autosize />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Clearable
Input with a clear button to reset its value.
<template>
<HLInput v-model:modelValue="value" clearable />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Password
Secure input field for password entry with toggle visibility.
<template>
<HLInput v-model:modelValue="value" type="password" />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Password reveal trigger
Use showPasswordOn to choose how the eye icon reveals the value. click (the default) toggles visibility and keeps the value shown until the icon is clicked again. mousedown reveals the value only while the pointer is held down — releasing or moving away hides it again.
<template>
<!-- Click the eye to toggle; stays visible until clicked again -->
<HLInput v-model:modelValue="value" type="password" show-password-on="click" />
<!-- Hold the eye to reveal; hides on release -->
<HLInput v-model:modelValue="value" type="password" show-password-on="mousedown" />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('hunter2')
</script>INFO
showPasswordOn only applies when type="password". On other input types there is no reveal icon for it to control.
Loading
Input field with loading indicator.
<template>
<HLInput v-model:modelValue="value" loading />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Character Limit
Input with maximum character limit enforcement.
<template>
<HLInput v-model:modelValue="value" :maxlength="10" />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Count characters
Input displaying current character count.
<template>
<HLInput v-model:modelValue="value" showCount />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Character Limit with Count
Input with both character limit and count display.
<template>
<HLInput v-model:modelValue="value" :maxlength="10" showCount />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Grapheme-aware count
By default, the character count and maxlength/minlength limits are based on the string's length in JavaScript, which counts UTF-16 code units. As a result, a single emoji or accented character made up of multiple code units is counted as more than one character. Provide a countGraphemes function (for example, one built on Intl.Segmenter) to count user-perceived characters (graphemes) instead. In the example below, the family emoji is counted as a single character, in contrast to the Character Limit with Count example above where it would count as several.
<template>
<HLInput v-model:modelValue="value" :maxlength="5" showCount :countGraphemes="countGraphemes" />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('👨👩👧👦')
// Count user-perceived characters (graphemes) instead of code units.
const countGraphemes = (input) => [...new Intl.Segmenter().segment(input)].length
</script>INFO
countGraphemes only has an effect alongside the props it customises: showCount (for the count display) and maxlength (for limit enforcement). On its own it does nothing.
Leading icon
Input with an icon at the start.
<template>
<HLInput v-model:modelValue="value" :prefixIcon="Mail01Icon" />
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
import { Mail01Icon } from '@gohighlevel/ghl-icons/24/outline'
const value = ref('')
</script>Payment input
Input with credit card logo prefix for payment forms.

<template>
<HLInput v-model:modelValue="value">
<template #prefix>
<div class="flex items-center" :style="{ width: '40px', height: '40px' }">
<img
src="https://download.logo.wine/logo/Mastercard/Mastercard-Logo.wine.png"
alt="credit card"
class="w-full h-full object-contain"
/>
</div>
</template>
</HLInput>
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Leading text
Input with text prefix for URL or similar inputs.
<template>
<HLInput v-model:modelValue="value">
<template #prefix>https://</template>
</HLInput>
</template>
<script setup>
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const value = ref('')
</script>Trailing button
Input with an action button appended.
<template>
<HLInputGroup>
<HLInput v-model:modelValue="value" />
<HLButton :size="24" variant="secondary">
<HLIcon style="margin-right:4px;fill:var(--gray-700)" :size="20">
<Copy05Icon style="color:var(--gray-700);stroke-width: 2.5;" />
</HLIcon>
<HLText :size="20" weight="semibold">Copy</HLText>
</HLButton>
</HLInputGroup>
</template>
<script setup>
import { ref } from 'vue'
import { HLInput, HLInputGroup, HLButton, HLText } from '@platform-ui/highrise'
import { Copy05Icon } from '@gohighlevel/ghl-icons/24/outline'
const value = ref('')
</script>Leading dropdown
Input with a dropdown menu at the start.
<template>
<HLInput v-model:modelValue="inputValue">
<template #prefix>
<HLDropdown @update:show="showDropdown" :disabled="false" :size="24" :options="options" @select="selectedValue">
<div class="flex items-center">
<span size="xs">{{ value }}</span>
<HLIcon
:style="{
transform: isDropdownOpen ? 'rotate(180deg)' : 'rotate(0deg)',
transition: 'all 0.3s',
marginLeft: '4px',
}"
:size="24"
>
<ChevronDownIcon />
</HLIcon>
</div>
</HLDropdown>
</template>
</HLInput>
</template>
<script setup>
import { ref } from 'vue'
import { HLInput, HLDropdown, HLIcon } from '@platform-ui/highrise'
import { ChevronDownIcon } from '@gohighlevel/ghl-icons/24/outline'
import { options } from './options'
const inputValue = ref('')
const value = ref('Option 1')
const isDropdownOpen = ref(false)
const selectedValue = temp => {
value.value = temp.label
}
const showDropdown = val => {
isDropdownOpen.value = val
}
</script>export const options = [
{ key: 'option1', label: 'Option 1' },
{ key: 'option2', label: 'Option 2' },
{ key: 'option3', label: 'Option 3' },
{ key: 'option4', label: 'Option 4' },
]Trailing dropdown
Input with a dropdown menu at the end.
<template>
<HLInput v-model:modelValue="inputValue" :suffix-icon="HelpCircleIcon" suffix-icon-tooltip-content="Help">
<template #suffix>
<HLDropdown @update:show="showDropdown" :disabled="false" :size="24" :options="options" @select="selectedValue">
<div class="flex items-center">
<span size="xs">{{ value }}</span>
<HLIcon
:style="{
transform: isDropdownOpen ? 'rotate(180deg)' : 'rotate(0deg)',
transition: 'all 0.3s',
marginLeft: '4px',
}"
:size="24"
>
<ChevronDownIcon />
</HLIcon>
</div>
</HLDropdown>
</template>
</HLInput>
</template>
<script setup>
import { ref } from 'vue'
import { HLInput, HLDropdown, HLIcon } from '@platform-ui/highrise'
import { ChevronDownIcon, HelpCircleIcon } from '@gohighlevel/ghl-icons/24/outline'
import { options } from './options'
const inputValue = ref('')
const value = ref('Option 1')
const isDropdownOpen = ref(false)
const selectedValue = temp => {
value.value = temp.label
}
const showDropdown = val => {
isDropdownOpen.value = val
}
</script>export const options = [
{ key: 'option1', label: 'Option 1' },
{ key: 'option2', label: 'Option 2' },
{ key: 'option3', label: 'Option 3' },
{ key: 'option4', label: 'Option 4' },
]Event Testing
This example shows how to test the events the component emits. Try the following:
- Type text to see the
@update:modelValueevent, then blur the field to see@change - Press a key to test the
@keydownevent - Focus and blur the input to test the
@focusand@blurevents
Migration note
@onBlur has been removed. Use the native @blur event instead.
Event Log:
<template>
<HLInput
v-model:modelValue="inputModelValue"
placeholder="Test events here..."
clearable
@change="addEventLog('Change event triggered')"
@keydown="addEventLog('Keydown event triggered')"
@focus="addEventLog('Focus event triggered')"
@blur="addEventLog('Blur event triggered')"
@update:modelValue="addEventLog(`Value updated: ${$event}`)"
/>
<div class="text-sm">
<p class="font-bold mb-2">Event Log:</p>
<div v-if="eventLog.length === 0" class="text-gray-500">No events logged yet. Try the actions above.</div>
<div v-for="(log, index) in eventLog" :key="index" class="text-gray-700">{{ log.timestamp }}: {{ log.event }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLInput } from '@platform-ui/highrise'
const inputModelValue = ref('')
const eventLog = ref<{ event: string; timestamp: string }[]>([])
const addEventLog = (event: string) => {
eventLog.value.unshift({ event, timestamp: new Date().toLocaleTimeString() })
// Keep only last 5 events
if (eventLog.value.length > 5) {
eventLog.value.pop()
}
}
</script>Accessibility
- Connect the field to visible labels via
id/foror supplyaria-labelwhen the label is hidden. - Reference helper and error copy through
aria-describedby, and togglearia-invalidwhen validation fails. - Manage suggestion lists with
aria-expanded,aria-controls, and (when applicable)aria-autocompleteso assistive tech understands the relationship. - Use
inputPropsprop to pass attributes to the internal input element (<input>, or<textarea>whentype="textarea").
Imports
import { HLInput } from '@platform-ui/highrise'Props
| Name | Type | Default | Description |
|---|---|---|---|
| id * | string | undefined | undefined | The id of the element |
| modelValue | string | '' | The value of the input |
| type | 'text' | 'textarea' | 'password' | 'tel' | 'email' | 'url' | 'text' | The type of the input |
| readonly | boolean | false | If true, the input is read-only |
| rows | number | 3 | Number of rows for textarea |
| clearable | boolean | false | If true, a clear button is shown |
| size | 'lg' | 'md' | 'sm' | 'xs' | '2xs' | '3xs' | 'sm' | The size of the input |
| placeholder | string | undefined | undefined | Placeholder text for the input |
| autosize | boolean | AutosizeConfig | undefined | undefined | If true, the textarea auto-resizes |
| maxlength | number | undefined | undefined | Maximum number of characters allowed |
| minlength | number | undefined | undefined | Minimum number of characters required |
| loading | boolean | undefined | undefined | If true, a loading indicator is shown |
| showCount | boolean | false | If true, character count is shown |
| countGraphemes | ((value: string) => number) | undefined | undefined | Counts characters as graphemes (emoji/combining marks count as one) for the count display and maxlength enforcement |
| disabled | boolean | undefined | undefined | If true, the input is disabled |
| autofocus | boolean | false | If true, the input is focused on mount |
| inputProps | Object | undefined | Additional attributes applied to the native input element |
| showPasswordOn | 'mousedown' | 'click' | 'click' | Event that reveals the value when type="password" |
| prefixIcon | string | Component | undefined | undefined | Icon shown at the start — an image src or an icon component |
| suffixIcon | string | Component | 'dropdown' | undefined | undefined | Icon shown at the end — an image src, an icon component, or 'dropdown' |
| suffixIconTooltipContent | string | undefined | undefined | Tooltip content of suffix icon |
| textAlign | 'start' | 'center' | 'end' | 'start' | Text and placeholder alignment of the input |
| fontSize | string | undefined | undefined | Font size of the input |
| fontWeight | string | undefined | undefined | Font weight of the input |
Types
// Configuration for auto-sizing textarea
interface AutosizeConfig {
minRows?: number
maxRows?: number
}Type Details
AutosizeConfig
minRows: Minimum number of rows for textareamaxRows: Maximum number of rows for textarea
Emits
| Name | Default | Trigger |
|---|---|---|
@update:modelValue | (value: string | [string, string] | number | null) => void | When the input value changes |
@change | (value: string) => void | When native change event is fired |
@focus | (FocusEvent) => void | When the input is focused |
@blur | (FocusEvent) => void | When the input is blurred |
@keydown | (event: KeyboardEvent) => void | When the keydown event is fired |
Slots
| Name | Parameters | Description |
|---|---|---|
| prefix | () | Content shown before the input |
| suffix | () | Content shown after the input |
Methods
| Method | Type | Description |
|---|---|---|
focus() | () => void | Focuses the input |
blur() | () => void | Blurs the input |
select() | () => void | Selects the input |
scrollTo(value: number) | (value: number) => void | Scrolls the input to a specific position |