Skip to content
RTL Support: Full
Accessibility: Full
Translations: Not Needed
Migration Guide: Work in progress

Input Group

Group multiple inputs together with various display options.

Default

Wrap an HLInputGroupLabel and an HLInput in an HLInputGroup to join them into a single seamless control.


https://
example.com
Vue
html
<template>
  <HLInputGroup>
    <HLInputGroupLabel>https://</HLInputGroupLabel>
    <HLInput id="input-group-basic-input" v-model:model-value="value" placeholder="example.com" />
  </HLInputGroup>
</template>

<script setup lang="ts">
  import { HLInputGroup, HLInputGroupLabel, HLInput } from '@platform-ui/highrise'
  import { ref } from 'vue'
  const value = ref('')
</script>

Prefix and Suffix Labels

Place labels on both sides of an input to frame its value — for example a full URL.


https://www.
Please Input
.com
Vue
html
<template>
  <HLInputGroup>
    <HLInputGroupLabel>https://www.</HLInputGroupLabel>
    <HLInput id="input-group-affix-input" v-model:model-value="value" />
    <HLInputGroupLabel>.com</HLInputGroupLabel>
  </HLInputGroup>
</template>

<script setup lang="ts">
  import { HLInputGroup, HLInputGroupLabel, HLInput } from '@platform-ui/highrise'
  import { ref } from 'vue'
  const value = ref('')
</script>

Sizes

Set size on the group and it cascades to every child that doesn't set its own. The group supports six sizes: lg, md, sm (default), xs, 2xs, and 3xs.

The group sets a baseline size, and any child can opt out by setting its own. Each input, select, and label picks its size from the first of these that is set:

  1. Its own size prop — if a child sets size directly, that wins.
  2. The group's size — otherwise the child inherits from the HLInputGroup around it, which is sm unless you set it.

INFO

A group always sets a size for its children, so a group inside an HLForm does not pick up the form's size — it stays sm until you set size on the group itself.


https://www.
Please Input
https://www.
Please Input
https://www.
Please Input
https://www.
Please Input
https://www.
Please Input
https://www.
Please Input
Vue
html
<template>
  <HLInputGroup size="lg">
    <HLInputGroupLabel>https://www.</HLInputGroupLabel>
    <HLInput id="input-group-size-lg" v-model:model-value="value" />
  </HLInputGroup>
  <HLInputGroup size="md"><!-- … --></HLInputGroup>
  <HLInputGroup size="sm"><!-- … --></HLInputGroup>
  <HLInputGroup size="xs"><!-- … --></HLInputGroup>
  <HLInputGroup size="2xs"><!-- … --></HLInputGroup>
  <HLInputGroup size="3xs"><!-- … --></HLInputGroup>
</template>
<script setup lang="ts">
  import { HLInputGroup, HLInputGroupLabel, HLInput } from '@platform-ui/highrise'
  import { ref } from 'vue'
  const value = ref('')
</script>

Select with Input

Lead an input with a select to qualify what the user is typing. A common pattern is a scoped search — pick what to search, type the query, and trigger it with a trailing button.


Search…
Vue
html
<template>
  <HLInputGroup style="max-width:520px">
    <HLSelect id="input-group-search-scope" v-model:value="scope" :options="scopeOptions" />
    <HLInput id="input-group-search-term" v-model:model-value="term" placeholder="Search…" />
    <HLButton id="input-group-search-button">
      <template #iconLeft><SearchLgIcon /></template>
      Search
    </HLButton>
  </HLInputGroup>
</template>
<script setup lang="ts">
  import { HLInputGroup, HLInput, HLSelect, HLButton } from '@platform-ui/highrise'
  import { SearchLgIcon } from '@gohighlevel/ghl-icons/24/outline'
  import { ref } from 'vue'
  const scope = ref('contacts')
  const term = ref('')
  const scopeOptions = [
    { label: 'Contacts', value: 'contacts' },
    { label: 'Opportunities', value: 'opportunities' },
    { label: 'Conversations', value: 'conversations' },
  ]
</script>

Select with Input Number

Pair a currency select with a number input for money fields in billing, invoicing, or product setup.


0.00
Vue
html
<template>
  <HLInputGroup style="max-width:320px">
    <HLSelect id="input-group-currency" v-model:value="currency" :options="currencyOptions" style="max-width:110px" />
    <HLInputNumber id="input-group-amount" v-model:value="amount" :min="0" placeholder="0.00" />
  </HLInputGroup>
</template>
<script setup lang="ts">
  import { HLInputGroup, HLInputNumber, HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'
  const currency = ref('usd')
  const amount = ref(null)
  const currencyOptions = [
    { label: 'USD', value: 'usd' },
    { label: 'EUR', value: 'eur' },
    { label: 'GBP', value: 'gbp' },
  ]
</script>

Multiple Inputs

Combine several inputs into one seamless control — for example a first / middle / last name field.


First name
Middle name
Last name
Vue
html
<template>
  <HLInputGroup style="max-width:600px">
    <HLInput id="input-group-first-name" v-model:model-value="firstName" placeholder="First name" />
    <HLInput id="input-group-middle-name" v-model:model-value="middleName" placeholder="Middle name" />
    <HLInput id="input-group-last-name" v-model:model-value="lastName" placeholder="Last name" />
  </HLInputGroup>
</template>
<script setup lang="ts">
  import { HLInputGroup, HLInput } from '@platform-ui/highrise'
  import { ref } from 'vue'
  const firstName = ref('')
  const middleName = ref('')
  const lastName = ref('')
</script>

Multiple Selects

Chain selects together for dependent choices like country and state.


Country
State
Vue
html
<template>
  <HLInputGroup style="max-width:400px">
    <HLSelect id="input-group-country" v-model:value="country" :options="countryOptions" placeholder="Country" />
    <HLSelect id="input-group-state" v-model:value="state" :options="stateOptions" placeholder="State" />
  </HLInputGroup>
</template>
<script setup lang="ts">
  import { HLInputGroup, HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'
  const country = ref(null)
  const state = ref(null)
  const countryOptions = [
    { label: 'United States', value: 'us' },
    { label: 'Canada', value: 'ca' },
    { label: 'Mexico', value: 'mx' },
  ]
  const stateOptions = [
    { label: 'California', value: 'ca' },
    { label: 'Texas', value: 'tx' },
    { label: 'New York', value: 'ny' },
  ]
</script>

Input and Button


Please Input
Vue
html
<template>
  <HLInputGroup style="max-width:400px">
    <HLInput id="input-group-copy-input" v-model:model-value="value" />
    <HLButton id="input-group-copy-button" @click="onCopyClick"><Copy01Icon class="w-6" />&nbsp;Copy</HLButton>
  </HLInputGroup>
</template>
<script setup lang="ts">
  import { HLInputGroup, HLInput, HLButton } from '@platform-ui/highrise'
  import { Copy01Icon } from '@gohighlevel/ghl-icons/24/outline'
  import { ref } from 'vue'

  const value = ref('')
  const onCopyClick = async () => {
    await navigator.clipboard.writeText(value.value)
  }
</script>

Input Group with Form Validation


https://www.
Please Input
.com
Vue
html
<template>
  <HLForm>
    <HLFormItem id="input-group-form" label="Website URL" validation-status="error" feedback="Enter your website URL">
      <HLInputGroup>
        <HLInputGroupLabel>https://www.</HLInputGroupLabel>
        <HLInput id="input-group-form-input" v-model:model-value="value" />
        <HLInputGroupLabel>.com</HLInputGroupLabel>
      </HLInputGroup>
    </HLFormItem>
  </HLForm>
</template>
<script setup lang="ts">
  import { HLForm, HLFormItem, HLInputGroup, HLInputGroupLabel, HLInput } from '@platform-ui/highrise'
  import { ref } from 'vue'
  const value = ref('')
</script>

Design Guidelines

Input components use a box-shadow to render their focus ring. Box-shadows render outside the element's bounds and may be clipped by any ancestor using overflow: hidden (e.g. Tab Panels or Dropdown Menus).

To prevent this, add a small gutter padding to the component's wrapper to ensure there is enough room for the focus ring to render without being cut off.

vue
<div class="p-[3px]">
  <!-- Your component here -->
</div>
  • The input group stretches to fill the width of its container. Constrain it by wrapping it (or setting max-width / width on the group) when you need a smaller footprint.
  • Field elements (inputs, selects, number, phone, tag) share the available width equally — each takes an equal portion of the row and shrinks together as space tightens. Labels and buttons keep their natural, content-based width.

Accessibility

  • Use a <fieldset> / role="group" with aria-labelledby to describe the grouped inputs.
  • Attach prefix/suffix explanations or error copy via aria-describedby on the group container.
  • Toggle aria-invalid on the group when any child input fails validation so the entire control is flagged.

Imports

ts
import { HLInputGroup, HLInputGroupLabel } from '@platform-ui/highrise'

Props

HLInputGroup Props

NameTypeDefaultDescription
size'lg' | 'md' | 'sm' | 'xs' | '2xs' | '3xs''sm'Size of the group. Cascades to child inputs, selects, and labels that don't set their own size.

HLInputGroupLabel Props

NameTypeDefaultDescription
size'lg' | 'md' | 'sm' | 'xs' | '2xs' | '3xs'Inherits from parentSize of the label. When unset, it inherits from the parent input group, defaulting to sm.

Slots

HLInputGroup Slots

NameParametersDescription
default()The default content slot. Can contain HLInput, HLSelect, HLButton, HLInputGroupLabel

HLInputGroupLabel Slots

NameParametersDescription
default()The default content slot for the label text