Input

A compound text field with a label, optional hint, prefix icons, suffix icons, prefix labels, and interactive suffix buttons. Built with slots and Context - each part is a named export.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add input

Usage#

usage.tsxTSX
import {  InputRoot,  InputField,  InputLabel,} from "@/components/ui/input"export function Example() {  return (    <div className="flex flex-col gap-1.5">      <InputLabel htmlFor="email">Email</InputLabel>      <InputRoot>        <InputField id="email" placeholder="you@example.com" type="email" />      </InputRoot>    </div>  )}

Playground#

The props dress the shell; the parts are everything around the field. A label, a hint, a leading icon and a trailing slot are all children you compose, so each one you switch off disappears from the code.

Lowercase letters and dashes.

Playground
Props

A bordered shell, or a bare inline field.

Height comes from size alone, never from density.

Destructive ring, and the hint turns with it.

Dims the shell and blocks input.

Parts

The field's name, wired by htmlFor.

A leading icon inside the shell.

A trailing slot inside the shell.

Helper text under the field.

input.tsxTSX
<div className="w-full max-w-xs space-y-1.5">  <InputLabel htmlFor="playground">Workspace</InputLabel>  <InputRoot>    <InputField id="playground" placeholder="koala-design" />  </InputRoot>  <InputHint>    <Component swapKey="idle" align="start">      Lowercase letters and dashes.    </Component>  </InputHint></div>

Label & hint#

InputLabel sits above the control - wire it to the field with htmlFor pointing at the InputField’s id so clicking the label focuses the input. Pass required to append a destructive asterisk. InputHint renders helper text below; give it an id and reference it from the field’s aria-describedby.

Used in your team URL. Lowercase letters and dashes only.

Prefix icon#

Use InputPrefix to place a decorative icon on the left. The slot sizes any svg automatically and marks it aria-hidden so it stays invisible to assistive tech.

Suffix icon#

Use InputSuffix to place a decorative icon on the right - useful for status indicators or type hints.

Prefix label#

InputPrefixLabel renders a fixed text segment flush against the left border - ideal for URL schemes, currency symbols, or units. It uses a negative margin to reach the container edge and overflow-hidden on the root clips it at the rounded corner.

https://

Suffix button#

InputSuffixButton renders an interactive ghost button inside the field - e.g. a clear action. Always pass aria-label since there is no visible text.

Password#

PasswordInput is a convenience wrapper that manages the show/hide toggle internally. It renders InputRoot + InputField + InputSuffixButton with an Eye / EyeSlash icon from Phosphor. Pass rootClassName to style the wrapper, and all other props go to the underlying input element.

Number#

NumberInput adds stepper controls that clamp to min/max, snap to step, and respond to ↑/↓ keys, click, and press-and-hold to repeat. The default stacked carets sit on the right; layout="inline" gives a − / + quantity stepper with full-height hit targets.

Quantity stepper#

The layout="inline" NumberInput is the − value + quantity stepper: compact, clamped at min, and keyboard- and hold-to-repeat friendly. It's the control behind a cart row's OrderSummaryItemQuantity (see Order Summary → Shopping cart). Give it a fixed rootClassName width so it stays tidy in a row.

Phone#

PhoneInput pairs the field with a searchable country picker - circular flags, type-to-filter, full keyboard navigation, and a live +code prefix. onChange reports a { country, dialCode, nationalNumber, e164 } payload; store e164 as the canonical value. The country segment takes the field's resolved size, so inside a Form it grows with the other controls and its hover fills flush to the border.

E.164: +15550142

Sizes#

Three sizes on a 4px grid - sm (32 px), md (36 px, the default), and lg (40 px). Set size on InputRoot; all parts pick it up from context.

Error#

Pass hasError to InputRoot to switch the border and focus ring to the destructive color and set aria-invalid on the underlying input. Pass the same hasError to InputHint so the helper text doubles as the error message.

Enter a valid email address.

Disabled#

Inline#

Pass variant="inline" to InputRoot for a chromeless field: the border and fill stay invisible at rest and the container only materializes on hover, while focus keeps the usual brand ring. Perfect for edit-in-place UI - editable titles, table cells, or settings rows that should read as plain text until you reach for them.

FAQ#

`InputRoot` is the bordered container that owns the size, error, and disabled state and shares it via context, while `InputField` is the actual `<input>`. Splitting them lets `InputPrefix`, `InputSuffix`, and buttons sit inside the same border as siblings of the field.