Pho Design System

Input

A single-line text field built on Base UI Field, so the label, helper text, and control are wired together for accessibility. It adds Pho’s bordered surface, a leading icon slot, an invalid state, and a built-in password reveal toggle. For multi-field layouts and validation, compose it inside a Form.

We'll never share it.

That username is already taken.

Import

import { Input } from "@photon-ai/pho-ui/components/input";

Sizes

Three sizes — sm, md, lg — scaling padding and text. The default is md. Height is the line-height plus vertical padding (the ring is drawn inset, so it adds no height), which lands lg at 44px — matching the lg Button and the auth pill. Corner radius is rounded-base (12px) at every size; lg also gets squircle smoothing.

Size Padding (effective) Text (size / line-height) Height Radius
sm 12 / 8px text-base — 14 / 20px 36px rounded-base (12px)
md 12 / 8px text-md — 16 / 24px 40px rounded-base (12px)
lg 14 / 10px text-md — 16 / 24px 44px squircle rounded-base (12px)

Corners nest concentrically (inner radius = outer radius − inset): the wrapper insets the control 2px so its autofill fill rounds at outer − 2px inside the ring, and at lg the hairline’s inner edge is held at 11px (rest) / 10px (focused) rather than the browser’s superellipse inset-shadow derivation, which otherwise thickens at the corners. The effective padding above already includes the 2px inset.

<Input size="sm" label="Small" />
<Input size="md" label="Medium" />
<Input size="lg" label="Large" />

Leading icon

icon takes an icon component (e.g. a Tabler icon) and renders it inside the left edge of the field, with the control padded to clear it. The icon is decorative — the label still carries the meaning.

import { IconMail } from "@tabler/icons-react";

<Input
  label="Email"
  type="email"
  placeholder="[email protected]"
  icon={IconMail}
/>;

Password

type="password" adds a reveal toggle at the right edge that swaps the field between hidden and plain text. No extra wiring — pass the type and the toggle appears.

<Input label="Password" type="password" placeholder="••••••••" />

Invalid

invalid renders the error treatment: an error-colored ring, a warning icon, and the hint recolored to the error tone. Put the reason in hint so the field explains itself.

<Input
  label="Username"
  defaultValue="ada"
  invalid
  hint="That username is already taken."
/>

Disabled

disabled dims the field surface and blocks input; it flows through Base UI Field, though only the bordered surface dims — the label and hint keep their normal styling.

<Input label="Workspace" placeholder="Unavailable" disabled />

Label actions

labelTrailing places a node at the right edge of the label row — a secondary action like a “Forgot?” link — without crowding the field itself. Mark a required field with required, which adds a brand asterisk and the native required attribute.

Forgot?
<Input
  label="Password"
  type="password"
  labelTrailing={
    <a href="/reset" className="text-pho-brand text-base font-medium">
      Forgot?
    </a>
  }
/>

Props

Accessibility

Built on Base UI Field, which associates the parts for you:

Keyboard:

Focus shows a 2px brand ring on :focus-visible (solid error when invalid). The resting invalid edge is ring-pho-error-subtle at 1.5px — between the 1px hairline and the 2px focus ring — so the error reads without competing with focus.

Best practices