<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhCheckCircle } from "@phosphor-icons/vue";
import { onBeforeUnmount, ref } from "vue";
const status = ref("success");
const subdomain = ref("phi");
let timer;
const handleChange = (value) => {
subdomain.value = value;
if (timer) window.clearTimeout(timer);
if (value.length > 0) {
status.value = "loading";
timer = window.setTimeout(() => {
status.value = "success";
}, 1500);
} else {
status.value = "idle";
}
};
onBeforeUnmount(() => {
if (timer) window.clearTimeout(timer);
});
</script>
<template>
<InputGroup>
<InputGroup.Input
:model-value="subdomain"
maxlength="20"
aria-label="Project subdomain"
@update:model-value="handleChange"
/>
<InputGroup.Suffix>.example.com</InputGroup.Suffix>
<InputGroup.Addon v-if="status !== 'idle'" align="end">
<span v-if="status === 'loading'" class="phi-input-group-spinner" aria-label="Loading" role="status" />
<PhCheckCircle v-else weight="duotone" />
</InputGroup.Addon>
</InputGroup>
</template>Installation
Barrel
import { InputGroup } from "@dicehub/phi";Granular
import { InputGroup } from "@dicehub/phi/components/input-group";Usage
With Built-in Field (Recommended)
Pass the label prop to InputGroup to enable the built-in Field wrapper with label, description, and error support.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhMagnifyingGlass } from "@phosphor-icons/vue";
</script>
<template>
<InputGroup label="Search" description="Find pages, components, and more">
<InputGroup.Addon>
<PhMagnifyingGlass />
</InputGroup.Addon>
<InputGroup.Input placeholder="Search..." />
</InputGroup>
</template>Bare InputGroup (Custom Layouts)
For custom form layouts, use InputGroup without label. Must providearia-label or aria-labelledby on InputGroup.Input for accessibility.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhMagnifyingGlass } from "@phosphor-icons/vue";
</script>
<template>
<InputGroup>
<InputGroup.Addon>
<PhMagnifyingGlass />
</InputGroup.Addon>
<InputGroup.Input placeholder="Search..." aria-label="Search" />
</InputGroup>
</template>Examples
Icon
Use Addon to place an icon at the start of the input as a visual identifier.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhLink } from "@phosphor-icons/vue";
</script>
<template>
<InputGroup>
<InputGroup.Addon>
<PhLink />
</InputGroup.Addon>
<InputGroup.Input placeholder="Paste a link..." aria-label="Link" />
</InputGroup>
</template>Text
Use Addon to place text prefixes or suffixes alongside the input.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
</script>
<template>
<div class="stack">
<InputGroup>
<InputGroup.Addon>@</InputGroup.Addon>
<InputGroup.Input placeholder="username" aria-label="Username" />
</InputGroup>
<InputGroup>
<InputGroup.Input placeholder="email" aria-label="Email" />
<InputGroup.Addon align="end">@example.com</InputGroup.Addon>
</InputGroup>
<InputGroup>
<InputGroup.Addon>/api/</InputGroup.Addon>
<InputGroup.Input placeholder="endpoint" aria-label="API path" />
<InputGroup.Addon align="end">.json</InputGroup.Addon>
</InputGroup>
</div>
</template>Button
Place InputGroup.Button inside an Addon for actions that operate directly on the input value. Buttons stay flat inside the shared group surface.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhEye, PhEyeSlash, PhMagnifyingGlass, PhX } from "@phosphor-icons/vue";
import { ref } from "vue";
const show = ref(false);
const searchValue = ref("search");
</script>
<template>
<div class="stack">
<InputGroup>
<InputGroup.Input :type="show ? 'text' : 'password'" default-value="password" aria-label="Password" />
<InputGroup.Addon align="end">
<InputGroup.Button
shape="square"
:icon="show ? PhEyeSlash : PhEye"
:aria-label="show ? 'Hide password' : 'Show password'"
@click="show = !show"
/>
</InputGroup.Addon>
</InputGroup>
<InputGroup>
<InputGroup.Addon><PhMagnifyingGlass /></InputGroup.Addon>
<InputGroup.Input v-model="searchValue" placeholder="Search" aria-label="Search" />
<InputGroup.Addon v-if="searchValue" align="end">
<InputGroup.Button shape="square" :icon="PhX" aria-label="Clear search" @click="searchValue = ''" />
</InputGroup.Addon>
<InputGroup.Button variant="secondary">Search</InputGroup.Button>
</InputGroup>
</div>
</template>Button with Tooltip
Pass a tooltip prop to InputGroup.Button to show a tooltip on hover.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhMagnifyingGlass, PhQuestion } from "@phosphor-icons/vue";
</script>
<template>
<InputGroup>
<InputGroup.Addon>
<PhMagnifyingGlass />
</InputGroup.Addon>
<InputGroup.Input placeholder="Search with query language..." aria-label="Search" />
<InputGroup.Addon align="end">
<InputGroup.Button
shape="square"
:icon="PhQuestion"
aria-label="Query language help"
tooltip="Query language help"
/>
</InputGroup.Addon>
</InputGroup>
</template>Kbd
Place a keyboard shortcut hint inside an end Addon.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhMagnifyingGlass } from "@phosphor-icons/vue";
</script>
<template>
<InputGroup>
<InputGroup.Addon>
<PhMagnifyingGlass />
</InputGroup.Addon>
<InputGroup.Input placeholder="Search..." aria-label="Search" />
<InputGroup.Addon align="end">
<kbd class="phi-input-group-kbd">⌘K</kbd>
</InputGroup.Addon>
</InputGroup>
</template>Loading
Place a loader inside an end Addon as a status indicator while validating the input value.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
</script>
<template>
<InputGroup>
<InputGroup.Input default-value="phi" aria-label="Project slug" />
<InputGroup.Addon align="end">
<span class="phi-input-group-spinner" aria-label="Loading" role="status" />
</InputGroup.Addon>
</InputGroup>
</template>Inline Suffix
Suffix renders text that flows seamlessly next to the typed value.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhCheckCircle, PhXCircle } from "@phosphor-icons/vue";
</script>
<template>
<div class="stack">
<InputGroup label="Subdomain">
<InputGroup.Input default-value="phi" maxlength="20" />
<InputGroup.Suffix>.example.com</InputGroup.Suffix>
<InputGroup.Addon align="end">
<PhCheckCircle weight="duotone" />
</InputGroup.Addon>
</InputGroup>
<InputGroup label="Subdomain" error="This subdomain is unavailable">
<InputGroup.Input default-value="taken" maxlength="20" />
<InputGroup.Suffix>.example.com</InputGroup.Suffix>
<InputGroup.Addon align="end">
<PhXCircle weight="duotone" />
</InputGroup.Addon>
</InputGroup>
</div>
</template>Sizes
Four sizes: xs, sm, base (default), and lg. The size applies to the entire group. Large groups use a 2px outer inset for trailing addon buttons.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhMagnifyingGlass, PhQuestion } from "@phosphor-icons/vue";
</script>
<template>
<div class="stack">
<InputGroup size="xs" label="Extra Small">
<InputGroup.Addon><PhMagnifyingGlass /></InputGroup.Addon>
<InputGroup.Input placeholder="Extra small input" />
<InputGroup.Addon align="end">
<InputGroup.Button shape="square" :icon="PhQuestion" aria-label="Help" />
</InputGroup.Addon>
</InputGroup>
<InputGroup size="sm" label="Small">
<InputGroup.Addon><PhMagnifyingGlass /></InputGroup.Addon>
<InputGroup.Input placeholder="Small input" />
<InputGroup.Addon align="end">
<InputGroup.Button shape="square" :icon="PhQuestion" aria-label="Help" />
</InputGroup.Addon>
</InputGroup>
<InputGroup label="Base (default)">
<InputGroup.Addon><PhMagnifyingGlass /></InputGroup.Addon>
<InputGroup.Input placeholder="Base input" />
<InputGroup.Addon align="end">
<InputGroup.Button shape="square" :icon="PhQuestion" aria-label="Help" />
</InputGroup.Addon>
</InputGroup>
<InputGroup size="lg" label="Large">
<InputGroup.Addon><PhMagnifyingGlass /></InputGroup.Addon>
<InputGroup.Input placeholder="Large input" />
<InputGroup.Addon align="end">
<InputGroup.Button shape="square" :icon="PhQuestion" aria-label="Help" />
</InputGroup.Addon>
</InputGroup>
</div>
</template>States
Use label, error, disabled, required, and description props directly on InputGroup.
<script setup>
import { InputGroup } from "@dicehub/phi/components/input-group";
import { PhEye, PhEyeSlash, PhMagnifyingGlass } from "@phosphor-icons/vue";
import { ref } from "vue";
const show = ref(false);
</script>
<template>
<div class="stack">
<InputGroup label="Error State" error="Please enter a valid email address">
<InputGroup.Input type="email" default-value="invalid-email" />
<InputGroup.Addon align="end">@example.com</InputGroup.Addon>
</InputGroup>
<InputGroup label="Disabled" disabled>
<InputGroup.Addon><PhMagnifyingGlass /></InputGroup.Addon>
<InputGroup.Input placeholder="Search..." />
</InputGroup>
<InputGroup label="Optional Field" :required="false">
<InputGroup.Addon>$</InputGroup.Addon>
<InputGroup.Input placeholder="0.00" />
</InputGroup>
<InputGroup label="With Description" description="Must be at least 8 characters" label-tooltip="Your password is stored securely">
<InputGroup.Input :type="show ? 'text' : 'password'" placeholder="Password" />
<InputGroup.Addon align="end">
<InputGroup.Button
shape="square"
:icon="show ? PhEyeSlash : PhEye"
:aria-label="show ? 'Hide password' : 'Show password'"
@click="show = !show"
/>
</InputGroup.Addon>
</InputGroup>
</div>
</template>API Reference
InputGroup
The root container that provides context to all child parts.
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | Visible field label. Enables the field wrapper. |
description | string | - | Helper text shown below the group when there is no error. |
error | string | { message: string; match?: InputErrorMatch } | - | Validation error message. Also applies error styling. |
invalid | boolean | false | Marks the group invalid without requiring an error message. |
size | "xs" | "sm" | "base" | "lg" | "base" | Controls group height, text size, spacing, and icon size. |
disabled | boolean | false | Disables all child input and button controls. |
required | boolean | - | Set false to show "(optional)" after the label. |
labelTooltip | string | - | Info text displayed next to the label on hover or focus. |
inputId | string | - | Overrides the generated id used to associate the label and input. |
InputGroup.Input
The text input element. It inherits size, disabled, label, and error context from InputGroup.
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | number | - | Controlled value used by v-model. |
defaultValue | string | number | - | Initial value for uncontrolled inputs. |
type | string | "text" | Native input type. |
placeholder | string | - | Native placeholder text. |
disabled | boolean | false | Disables this input. The parent disabled prop also applies. |
$attrs | InputHTMLAttributes | - | Native input attributes such as aria-label, maxlength, name, and autocomplete. |
InputGroup.Addon
Container for icons, text, or compact buttons positioned at the start or end of the input.
| Prop | Type | Default | Description |
|---|---|---|---|
align | "start" | "end" | "start" | Places the addon before or after the input. |
InputGroup.Button
Button for secondary actions like toggle, clear, or help.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | ButtonVariant | "ghost" | Button visual style. Direct child buttons can use secondary for action buttons. |
shape | ButtonShape | "base" | Use square for icon-only addon buttons. |
icon | Component | - | Icon component rendered by the button. |
tooltip | string | - | Hover/focus tooltip text. Also becomes aria-label when no label is provided. |
tooltipSide | "top" | "bottom" | "left" | "right" | "bottom" | Preferred tooltip side. |
InputGroup.Suffix
Inline text that flows seamlessly next to the typed value.
| Prop | Type | Default | Description |
|---|---|---|---|
default slot | string | VNode | - | Inline suffix text that follows the typed value. |
Events
| Event | Type | Description |
|---|---|---|
update:modelValue | (value: string) => void | Emitted by InputGroup.Input on native input for v-model. |
valueChange | (value: string) => void | String-only change event from InputGroup.Input. |
Validation Error Types
Structured errors accept a browser validity match key.
| Match |
|---|
boolean |
badInput |
customError |
patternMismatch |
rangeOverflow |
rangeUnderflow |
stepMismatch |
tooLong |
tooShort |
typeMismatch |
valid |
valueMissing |
Accessibility
Label Requirement
Prefer the label prop on InputGroup. For bare groups, providearia-label or aria-labelledby on InputGroup.Input.
Group Role
InputGroup renders with role="group" to associate the input with its addons for assistive technologies.