PixelInput

Single-line text input with label, hint, error message, tone/size/surface variants, prefix/suffix slots, joinable addons, clearable button, char counter, and loading state.

  • Controlled — value + onChange (React), v-model (Vue), [(value)] or forms (Angular) — or uncontrolled with defaultValue.
  • Inside-shell prefix/suffix slots plus outside-shell addonLeft/addonRight for joined groups.
  • Optional clearable × button, loading spinner, and showCount character counter.
  • tone, size, and surface follow the kit-wide design tokens.
  • Accessible: pairs aria-invalid + aria-describedby with hint/error text.
Status
stable
Category
forms
Since
v1.0.0

API

import { PixelInput } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
labelstring—Floating label rendered above the input shell.
hintstring—Helper text shown below the field. Hidden when error is set.
errorstring—Error message shown below the field; flips visual state to invalid.
tone'green' | 'cyan' | 'gold' | 'red' | 'purple' | 'pink' | 'neutral''neutral'Visual tone for focus ring + border emphasis. Default: 'neutral'.
size'sm' | 'md' | 'lg''md'Field height token. Default: 'md'.
surface'pixel' | 'linear'—Surface variant. Inherits from PxlKitSurfaceProvider when omitted.
iconReact.ReactNode—Legacy left-icon slot, rendered inside the input shell. Equivalent to prefix.
prefixReact.ReactNode—Content rendered INSIDE the input shell on the left (icon or short text).
suffixReact.ReactNode—Content rendered INSIDE the input shell on the right (icon or short text).
addonLeftReact.ReactNode—Element rendered OUTSIDE the input shell and joined to its left edge (e.g. button/select).
addonRightReact.ReactNode—Element rendered OUTSIDE the input shell and joined to its right edge.
clearableboolean—When true, shows a clear (×) button while the value is non-empty.
onClear() => void—Callback fired when the clear button is clicked.
showCountboolean | { max?: number }—Render a character counter under the input. true shows N; { max } shows N/max.
loadingboolean—When true, replaces the suffix with a spinner and disables the input.

Also takes the native attributes and event handlers of <input> (InputHTMLAttributes<HTMLInputElement>).

ref points to <input>.

Accessibility

WCAG target: 2.1 AA

  • textbox

Sets aria-invalid when error is provided and links the hint/error text it shows via aria-describedby, after any ids you pass (none while neither shows). The clear button has tabindex="-1" so keyboard users edit the value directly instead of tabbing through it.

Keyboard

KeyActionWhen
TabMove focus into / out of the input.—
EnterSubmit the surrounding form (native browser behavior).—

Usage

import { PixelInput } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelInput
      label="Username"
      placeholder="hero@pxlkit.xyz"
      hint="Your retro alias"
    />
  );
}

Examples

Default

import { PixelInput } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelInput
      label="Username"
      placeholder="hero@pxlkit.xyz"
      hint="Your retro alias"
    />
  );
}

Controlled

import { useState } from 'react';
import { PixelInput } from '@pxlkit/ui-kit';

export function Controlled() {
  const [value, setValue] = useState('');
  return (
    <PixelInput
      label="Email"
      type="email"
      placeholder="hero@pxlkit.xyz"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      tone="cyan"
    />
  );
}

Uncontrolled

import { PixelInput } from '@pxlkit/ui-kit';

export function Uncontrolled() {
  return (
    <PixelInput
      label="Display name"
      defaultValue="Pixel Hero"
      hint="Edit me — uncontrolled"
    />
  );
}

Tones

import { PixelInput } from '@pxlkit/ui-kit';

export function Tones() {
  return (
    <div className="flex flex-col gap-3">
      <PixelInput label="Neutral" tone="neutral" defaultValue="neutral" />
      <PixelInput label="Cyan" tone="cyan" defaultValue="cyan" />
      <PixelInput label="Green" tone="green" defaultValue="green" />
      <PixelInput label="Gold" tone="gold" defaultValue="gold" />
      <PixelInput label="Purple" tone="purple" defaultValue="purple" />
      <PixelInput label="Pink" tone="pink" defaultValue="pink" />
    </div>
  );
}

Sizes

import { PixelInput } from '@pxlkit/ui-kit';

export function Sizes() {
  return (
    <div className="flex flex-col gap-3">
      <PixelInput label="Small" size="sm" placeholder="sm" />
      <PixelInput label="Medium" size="md" placeholder="md" />
      <PixelInput label="Large" size="lg" placeholder="lg" />
    </div>
  );
}

Surfaces

import { PixelInput } from '@pxlkit/ui-kit';

export function Surfaces() {
  return (
    <div className="flex flex-col gap-3">
      <PixelInput label="Pixel" surface="pixel" placeholder="pixel surface" />
      <PixelInput label="Linear" surface="linear" placeholder="linear surface" />
    </div>
  );
}

Prefix & Suffix

import { PixelInput } from '@pxlkit/ui-kit';

export function WithPrefixSuffix() {
  return (
    <div className="flex flex-col gap-3">
      <PixelInput
        label="Amount"
        prefix={<span className="text-xs">$</span>}
        suffix={<span className="text-xs">USD</span>}
        defaultValue="42"
      />
      <PixelInput
        label="Search"
        prefix={<span aria-hidden>?</span>}
        placeholder="Find anything"
      />
    </div>
  );
}

Joined Addons

import { PixelInput } from '@pxlkit/ui-kit';

export function WithAddons() {
  return (
    <PixelInput
      label="Website"
      addonLeft={<span className="text-xs">https://</span>}
      addonRight={<span className="text-xs">.xyz</span>}
      defaultValue="pxlkit"
    />
  );
}

Clearable

import { useState } from 'react';
import { PixelInput } from '@pxlkit/ui-kit';

export function Clearable() {
  const [value, setValue] = useState('clear me');
  return (
    <PixelInput
      label="Clearable"
      clearable
      value={value}
      onChange={(e) => setValue(e.target.value)}
      onClear={() => setValue('')}
    />
  );
}

Loading

import { PixelInput } from '@pxlkit/ui-kit';

export function Loading() {
  return (
    <PixelInput
      label="Verifying handle"
      defaultValue="pxlhero"
      loading
    />
  );
}

Disabled

import { PixelInput } from '@pxlkit/ui-kit';

export function Disabled() {
  return (
    <PixelInput
      label="Disabled"
      defaultValue="cannot edit"
      disabled
    />
  );
}

With Error

import { PixelInput } from '@pxlkit/ui-kit';

export function WithError() {
  return (
    <PixelInput
      label="Email"
      defaultValue="not-an-email"
      error="Please enter a valid email address"
      tone="red"
    />
  );
}

Character Count

import { useState } from 'react';
import { PixelInput } from '@pxlkit/ui-kit';

export function WithCharCount() {
  const [value, setValue] = useState('Hello');
  return (
    <PixelInput
      label="Bio"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      showCount={{ max: 80 }}
      hint="Keep it short"
    />
  );
}

Related