stream/ui
Browse the library

Input

A single-line text field with an optional leading icon and an invalid state.

stableInput.tsx

Examples

Basic

As it appears on your invoices.

input/basic.tsx
import { Input } from "@streamui/react";

export default function InputBasic() {
  return (
    <div className="w-full max-w-xs">
      <label htmlFor="input-name" className="mb-2 block font-bold">
        Full name
      </label>
      <Input id="input-name" autoComplete="name" placeholder="Ada Lovelace" aria-describedby="input-name-hint" />
      <p id="input-name-hint" className="mt-2 text-sm text-ink-muted">
        As it appears on your invoices.
      </p>
    </div>
  );
}

With icon

input/with-icon.tsx
import { Search } from "lucide-react";
import { Input } from "@streamui/react";

export default function InputWithIcon() {
  return (
    <div className="w-full max-w-xs">
      <label htmlFor="input-search" className="sr-only">
        Search projects
      </label>
      <Input id="input-search" type="search" icon={<Search />} placeholder="Search projects…" />
    </div>
  );
}

Invalid and disabled

Enter a complete email address.

input/states.tsx
import { Mail } from "lucide-react";
import { Input } from "@streamui/react";

export default function InputStates() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-6">
      <div>
        <label htmlFor="input-invalid" className="mb-2 block font-bold">
          Work email
        </label>
        <Input
          id="input-invalid"
          type="email"
          icon={<Mail />}
          defaultValue="ada@"
          invalid
          aria-describedby="input-invalid-error"
        />
        <p id="input-invalid-error" className="mt-2 text-sm font-semibold">
          Enter a complete email address.
        </p>
      </div>
      <div>
        <label htmlFor="input-disabled" className="mb-2 block font-bold">
          Workspace ID
        </label>
        <Input id="input-disabled" defaultValue="ws_8f2k1" disabled />
      </div>
    </div>
  );
}

Installation

bun add @streamui/react
usage.tsx
import { Input } from "@streamui/react";

<label htmlFor="email">Email</label>
<Input id="email" type="email" />

API

PropTypeDefaultDescription
iconReactNode—Leading icon. Rendered aria-hidden.
invalidbooleanfalseSets aria-invalid and tints the field pink.
frameClassNamestring—Classes for the outer frame (border, width).
classNamestring—Classes for the <input> element itself.
...propsComponentProps<"input">—Every native input attribute.

Accessibility

  • Always pair with a <label>; use sr-only on it when the design has no visible label.
  • When invalid, link the error message with aria-describedby so it is read on focus.
  • The frame shows the focus ring, since the input itself has no visible outline.

Source

packages/react/src/Input.tsx
import type { ComponentProps, ReactNode } from "react";
import { cn } from "./cn";

export type InputProps = ComponentProps<"input"> & {
  /** Leading icon, e.g. `<Mail />`. Decorative; hidden from assistive tech. */
  icon?: ReactNode;
  /** Marks the field invalid (sets `aria-invalid`) and tints it. */
  invalid?: boolean;
  /** Classes for the outer frame. `className` goes to the `<input>`. */
  frameClassName?: string;
};

export function Input({
  icon,
  invalid,
  frameClassName,
  className,
  ...props
}: InputProps) {
  return (
    <div
      className={cn(
        "flex items-center border-2 border-ink bg-paper-raised shadow-brutal-sm transition-shadow duration-150",
        "focus-within:shadow-brutal focus-within:outline-3 focus-within:outline-offset-2 focus-within:outline-blue",
        "has-disabled:opacity-50 has-disabled:shadow-none",
        invalid && "bg-pink-soft",
        frameClassName,
      )}
    >
      {icon && (
        <span aria-hidden className="ml-3 shrink-0 text-ink-muted [&_svg]:size-4.5">
          {icon}
        </span>
      )}
      <input
        aria-invalid={invalid || undefined}
        className={cn(
          "h-11 w-full min-w-0 bg-transparent px-3 placeholder:text-ink-muted focus:outline-none disabled:cursor-not-allowed",
          className,
        )}
        {...props}
      />
    </div>
  );
}