affectUI › Components › Inputs

Text Field

One input for every form – label, hint and error in place, prefix and suffix, clear, show-password and copy buttons, a character count.

Free and open source (MIT). Install: npx shadcn@latest add https://www.affectui.com/r/text-field.json · Import from @/components/ui/text-field

When to use

  • Every single-line field in a form: names, emails, URLs, amounts, search, passwords, API keys
  • Settings pages, sign-up and onboarding – one field everywhere, so all your screens look and behave the same

When not to use

  • Longer text over several lines – use a textarea
  • Picking from a fixed list – use Combobox or a select
  • Short verification codes – OTP Input reads and pastes them better

Usage

import { useState } from "react";
import { TextField } from "@/components/ui/text-field";

export function WorkspaceForm() {
  const [slug, setSlug] = useState("");
  const valid = /^[a-z0-9-]*$/.test(slug);

  return (
    <form className="grid gap-4">
      <TextField label="Workspace name" name="name" maxLength={32} counter clearable required />
      <TextField
        label="Workspace URL"
        prefix="acme.app/"
        value={slug}
        onChange={(e) => setSlug(e.target.value)}
        hint="Lowercase letters, numbers and dashes."
        error={valid ? undefined : "Only lowercase letters, numbers and dashes."}
      />
      <TextField label="Password" type="password" name="password" autoComplete="new-password" />
      <TextField label="API key" value={apiKey} readOnly copyable />
    </form>
  );
}

Props

NameTypeDefaultDescription
labelReactNodeShown above the field; clicking it focuses the input
hintReactNodeA quiet line under the field – the error takes its place while there is one
errorReactNodeMarks the field invalid (red edge and a faint red fill, aria-invalid) and shows the message where the hint was
prefixReactNodeText or a 16px icon inside the field before the value – e.g. "https://" or a search icon
suffixReactNodeText or an icon after the value – e.g. ".acme.app", "USD" or "%"
size"sm" | "md" | "lg""md"32 · 36 · 40px high; taller on touch screens
optionalbooleanfalseAdds "Optional" next to the label
clearablebooleanfalseA small × (and Esc) that empties the field while it has a value; onChange fires as if the user had deleted the text
copyablebooleanfalseA copy button that turns into a green check – pair it with readOnly for keys, links and IDs
counterbooleanfalseShows "12/40" under the field, red once a prefilled value is over the limit; needs maxLength
typestring"text"Any input type – "password" adds a show/hide button, "search" drops the browser's own ×
onClear() => voidCalled after the × emptied the field
inputClassNamestringExtra class on the input itself, e.g. a monospace font
classNamestringExtra class on the wrapper
...input propsComponentProps<"input">Everything else – value, defaultValue, onChange, name, placeholder, required, disabled, readOnly, maxLength, autoComplete, ref – goes to the input

Keyboard

  • Tab: Moves into the field, then to its show-password or copy button – the × stays out of the way, Esc does its job
  • Esc: Empties a clearable field and keeps the caret in it
  • Enter / Space: On a button: shows or hides the password, or copies

Accessibility

  • A real <input> with a <label> tied to it – works with forms, autofill, password managers and screen readers
  • The hint and the error are linked with aria-describedby; an error sets aria-invalid and is announced politely when it appears
  • Clear, show password and copy are labelled buttons; the reveal button reports its state with aria-pressed and "Copied" is announced
  • Without a label, a text placeholder becomes the field's accessible name – better still, pass aria-label
  • Focus darkens the edge a step – no ring, the caret shows where you are; the buttons get a clear outline under keyboard focus. Need a stronger focus edge? Override --ui-tf(c)-edge-focus / --ui-tfc-shadow-focus
  • dir="auto" on the value and the message, so Arabic or Hebrew text reads the right way in any form; prefix, suffix and buttons follow the page direction

Responsive

  • Fills its container; long values, prefixes and messages never push it wider – a long prefix is cut at its start, so the part next to the value stays
  • On touch screens the field grows to a comfortable tap height, its buttons to 32px with a larger hit area, and it uses 16px text, so iOS doesn't zoom in on focus
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"

Requirements

  • React 19

Related

  • Combobox – Pick one or many from a searchable, grouped list – chips with avatars, highlighted matches, create-on-the-fly and full keyboard support
  • OTP Input – The verification code field done right – a gliding focus ring, paste and SMS autofill, a calm shake on errors and a morph into Verified
  • Button – Primary, secondary, ghost and destructive buttons with icons, sizes and a shimmering loading state
  • Stepper Form – A multi-step flow for onboarding and setup – steps that fill and check off, sliding steps, a card that morphs to each step, inline validation and a success state