Skip to content

Input

The Input component is a single, variant-driven field that switches between different input types based on the variant prop. Instead of importing separate components for each input type, you use one Input and set variant to get the field you need — from a basic text field to a currency input, phone number, date picker, OTP code, or rich text editor.

All variants share common props for labels, helper text, error handling, and disabled state. Each variant also accepts its own specialised props.

Loading Code Block...

A basic OTP input with a label and helper text.

This is an input field.
Loading Code Block...

The variant prop determines which input type is rendered. All variants share the same common props (label, helperText, error, disabled, etc.) and each variant adds its own specialised props documented below.

VariantDescription
defaultStandard text input with optional left/right icons.
currencyNumeric input with a currency prefix (e.g. GHS).
mobile-numberPhone number input with country code selector.
selectDropdown select with single or multi-select support.
counterNumeric stepper with increment/decrement buttons.
searchText input styled for search with optional icon placement.
otpOne-time password input with individual character cells.
dateDate picker with single date or date range selection.
rich-text-areaRich text editor with formatting toolbar.

Use the variant dropdown below to switch between input types — the preview and the matching code update together:

Set disabled to prevent user interaction. All variants support the disabled state with reduced opacity and cursor-not-allowed.

Loading Code Block...

Set error to true to apply error styling to the field, and pass the message via the errorMessage prop.

Please enter a valid email address
Loading Code Block...

Use helperText to display guidance below the input. The helperTextIconType prop controls which icon appears alongside the helper text.

Icon typeDescription
infoInformation icon (default)
warningWarning triangle icon
checkCheckmark icon for success/validation

Use the interactive demo below to switch between helper text icon variants and see the matching code update automatically:


These props are shared across all variants.

PropsDefaultTypeDescription
variant'default''default' | 'currency' | 'mobile-number' | 'select' | 'counter' | 'search' | 'otp' | 'date' | 'rich-text-area'Determines which input type is rendered. Each variant activates a different underlying component with its own specialised props.
label-stringLabel text displayed above the input field.
placeholder-stringPlaceholder text shown inside the input when empty.
disabledfalsebooleanDisables the input, preventing user interaction. Applies reduced opacity and cursor-not-allowed.
requiredfalsebooleanMarks the field as required. Displays an asterisk next to the label.
helperText-stringHelper text displayed below the input to provide guidance. Overridden when `error` is provided.
helperTextIconType'info''info' | 'warning' | 'check'Icon displayed alongside the helper text. Choose 'info' for guidance, 'warning' for alerts, or 'check' for validation success.
errorfalsebooleanWhen true, applies error styling to the input (red border + error helper colour). Use together with `errorMessage` for the message line.
errorMessage-stringMessage displayed below the input when `error` is true. Overrides `helperText` for the message line. Available across Currency, Phone, Select, DatePicker, Counter, Verification (OTP), and the shared `Input`.
hint-stringHint text displayed near the label, often used for additional context or character limits.
id-stringHTML id attribute for the input element.
name-stringHTML name attribute for form submission.

Additional props available when variant="default".

Additional props available when variant="currency".

PropsDefaultTypeDescription
currency'GHS'stringCurrency code displayed as a prefix in the input.
endsWithZerosfalsebooleanWhen true, formats the value to always end with two decimal zeros (e.g. 10.00).
hint-stringHint text displayed as a tooltip near the currency input.

Additional props available when variant="mobile-number".

PropsDefaultTypeDescription
countryCodeDisplayType'code''code' | 'flag'How the country code is displayed — as a dial code string or a flag emoji/icon.
intlfalsebooleanWhen true, enables international phone number format support with country selection.

Additional props available when variant="select".

PropsDefaultTypeDescription
options-Array<{ label: string; value: string }>Array of selectable options displayed in the dropdown.
multiplefalsebooleanWhen true, allows selecting multiple options.
showSearchfalsebooleanWhen true, shows a search input within the dropdown to filter options.
clearablefalsebooleanWhen true, allows clearing the currently selected option.
wrapfalsebooleanWhen true, selected values wrap onto multiple lines instead of scrolling horizontally.
onSelectionChange-(selected: string | string[]) => voidCallback fired when the user selects or deselects an option.
isOpen-booleanControlled open state for the dropdown. When provided, use `onOpenChange` to sync state.
onOpenChange-(open: boolean) => voidCallback fired when the dropdown opens or closes.
capped-booleanWhen true, limits the height of the multi-select chip container.

Additional props available when variant="counter".

PropsDefaultTypeDescription
min100numberMinimum allowed value for the counter.
max1000numberMaximum allowed value for the counter.
value0numberCurrent value of the counter.
step1numberIncrement/decrement step value.
controlsPosition'center''center' | 'right'Position of the increment/decrement controls relative to the input.
onCounterUp-(value: number) => voidCallback fired when the increment (plus) control is activated. Receives the new clamped value.
onCounterDown-(value: number) => voidCallback fired when the decrement (minus) control is activated. Receives the new clamped value.

Additional props available when variant="search". The variant="search" routes the unified Input to a dedicated SearchInput component under the hood, so you can keep using the unified Input API or import SearchInput directly when you only need the search field.

"@hubtel/react-ui/input"; import {SearchInput} from "@hubtel/react-ui/input";
<Input
variant="search"
placeholder="Search transactions"
showCloseIcon
onSearch={(value) => fetchResults(value)}
onClear={() => fetchResults("")}
/>
;
<SearchInput
placeholder="Search transactions"
showCloseIcon
onSearch={(value) => fetchResults(value)}
onClear={() => fetchResults("")}
/>
; ```
<Spacer size="1rem" />
<Table
client:only="react"
data={[
{
prop: "leftIcon",
type: "ReactNode",
default: "-",
description: "Icon displayed on the left side of the search input.",
reactOnly: true,
},
{
prop: "leftIcon",
type: "Component",
default: "-",
description:
"Icon component displayed on the left side of the search input.",
vueOnly: true,
},
{
prop: "rightIcon",
type: "ReactNode",
default: "-",
description: "Icon displayed on the right side of the search input.",
reactOnly: true,
},
{
prop: "rightIcon",
type: "Component",
default: "-",
description:
"Icon component displayed on the right side of the search input.",
vueOnly: true,
},
{
prop: "searchIconPosition",
type: "'left' | 'right'",
default: "-",
description:
"Position of the default search icon. Overridden when custom leftIcon or rightIcon is provided.",
reactOnly: true,
},
{
prop: "showCloseIcon",
type: "boolean",
default: "false",
description:
"Renders a close (clear) icon when the field has a value. Clicking it triggers `onClear`.",
reactOnly: true,
},
{
prop: "onSearch",
type: "(value: string) => void",
default: "-",
description:
"Called with the current value when the user submits a search (Enter key or explicit search affordance).",
reactOnly: true,
},
{
prop: "onClear",
type: "() => void",
default: "-",
description: "Called when the user clears the field via the close icon.",
reactOnly: true,
},
]}
/>
<Spacer size="2rem" />
### OTP variant props
Additional props available when `variant="otp"`.
<Table
data={[
{
prop: "inputLength",
type: "number",
default: "6",
description:
"Number of individual character cells rendered for the OTP code.",
},
{
prop: "hint",
type: "string",
default: "-",
description: "Hint text displayed near the OTP input.",
},
]}
/>
<Spacer size="2rem" />
### Date variant props
Additional props available when `variant="date"`.
<Table
data={[
{
prop: "mode",
type: "'single' | 'range'",
default: "'single'",
description:
"Selection mode 'single' for a single date or 'range' for a start/end date range.",
},
{
prop: "allowPastDates",
type: "boolean",
default: "false",
description: "When true, allows selection of dates in the past.",
},
{
prop: "minDate",
type: "Date | string",
default: "-",
description: "Minimum selectable date. Dates before this are disabled.",
},
{
prop: "maxDate",
type: "Date | string",
default: "-",
description: "Maximum selectable date. Dates after this are disabled.",
},
{
prop: "maxSelectableMonths",
type: "number",
default: "3",
description: "Maximum span in months for date range selection.",
},
]}
/>
<Spacer size="2rem" />
### Rich text area variant props
Additional props available when `variant="rich-text-area"`.
<Table
data={[
{
prop: "editorHeight",
type: "string",
default: "'100px'",
description: "CSS height of the rich text editor area.",
},
]}
/>
<Spacer size="2rem" />
## Accessibility
- All input variants render with an associated `<label>` element when the `label` prop is provided, ensuring screen readers can announce the field purpose.
- The `required` prop adds visual indication (asterisk) and semantic attributes.
- Error messages are displayed inline below the input with appropriate styling for visibility.
- Helper text icons (`info`, `warning`, `check`) provide visual cues that complement the text content — the text itself carries the information.
- The OTP variant supports keyboard navigation between cells (auto-advance on input, backspace navigation).
- The select variant uses keyboard-accessible dropdown controls.
<Spacer size="2rem" />
## Best practices
:::tip[Variant selection]
Choose the most specific variant for your use case rather than building custom logic with the `default` variant. For example, use `variant="currency"` for monetary inputs instead of adding a prefix manually.
:::
:::tip[Error messages]
Provide specific, actionable error messages — "Email must include an @ symbol" is better than "Invalid input". The `error` prop automatically overrides `helperText`, so you don't need to manage both states manually.
:::
:::tip[Helper text]
Always include `helperText` for complex inputs (currency, date range, OTP) to set expectations about the expected format.
:::
:::caution[Variant switching]
Do not change the `variant` prop dynamically at runtime based on user input. Each variant initialises its own internal state, and switching variants without a remount can cause unexpected behaviour.
:::