Input

Text, textarea, dimension, and tag fields with built-in label, hint, and error treatment.

The project type will be added automatically

Unavailable.

Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/input.json
Usage
Each field is a single component that owns its label, hint, and error treatment. Drop in `iconLeading` / `iconTrailing` for inline glyphs.
TSImport
import {
  TextField,
  TextareaField,
  NumberInput,
  FileSizeInput,
  TagsInput,
  DurationInput,
  RepeatableInput,
} from "@/components/ui/input";
TSExample
import { MagnifyingGlass as PhMagnifyingGlass } from "@phosphor-icons/react/ssr";
import { TextField } from "@/components/ui/input";

const SearchIcon = (p: { className?: string }) => <PhMagnifyingGlass weight="duotone" {...p} />;

export function Example() {
  return (
    <TextField
      label="Project title"
      placeholder="Enter project title…"
      caption="The project type will be added automatically."
      iconLeading={SearchIcon}
      required
    />
  );
}
Composition
Anatomy of the input primitives.
TextField                       (label + caption + input in one component)
├── Label                       (renders if `label` is passed)
├── Caption                     (renders if `caption` is passed — under the label)
├── (iconLeading)               (lucide icon component)
├── <input>
├── (iconTrailing)              (lucide icon component)
└── error                       (rendered below the control in red)

TextareaField                   (same surface, multi-line + optional character count)
FileSizeInput                   (width × height + unit selector)
TagsInput                       (chip list driven by Enter / Tab / , / paste)
DurationInput                   (segmented d/h/m/s field, value in total seconds)
Text field
Compound component with label, hint, and error — ported from PRF4’s Untitled UI field.

The project type will be added automatically

Unavailable.

Input types
Every native HTML type the team regularly reaches for, using the shadcn Input primitive.

Your unique identifier

Validates format on blur.

US format auto-applies as you type.

Must be at least 8 characters

3- or 4-digit account number.

Email verification
The email `TextField` wired to a live deliverability check (rapid-email-verifier via a proxy route). Typing is debounced 500ms: a spinner shows while checking, a green ✓ when the address is valid, and a red ✗ with an error message when it can't receive mail.

We'll only use this to contact you about your project.

Number field
Numeric input with stepper buttons. Pass `min`, `max`, and `step` for range/granularity. Drives the number-field renderer in @thesqd/dynamic-forms.

Maximum 50 seats per workspace.

File size
Width × height with a unit selector (inches, cm, mm, pixels, points). Pair with a Label for document dimensions, canvas sizes, or print bleeds.

Output: {"h":null,"w":null,"unit":"in"}

Output: {"h":null,"w":null,"unit":"px"}

pixels

Click the pencil to split into editable cards; ✓ to converge.

Textareas
Multi-line text input with and without validation.

Tell us a bit about yourself.

Uses Chrome's Web Speech API — button hides in other browsers.

Tags input
Multi-value input that commits each entry as a Squad Badge chip on Enter / Tab / comma. Backspace on an empty draft removes the previous chip; paste a comma- or newline-separated list to bulk-add.

Press Enter / Tab / , to add. Backspace to remove the last tag.

Sermon seriesEasterOutreach

The fourth tag is silently rejected.

VolunteersWorship
Duration input
Segmented time-duration field with configurable units (d/h/m/s). Segments auto-advance on fill and support keyboard navigation.

Output: 3600s

Output: 7384s

Output: 120s

Output: 90000s

Output: 3600–7200s

Output: 90–240s

Output: 120–240s

Repeatable input
A single input that grows into a list. Click the + ghost button next to any row to insert another input below it; the response is a `string[]`. Set `min` (defaults to 1) and an optional `max` to bound the row count.

Click “+ Add another” to add a URL. Outputs a string[].

Min 2, max 5.

Grouped repeatable input
`GroupedRepeatableInput` puts toggle chips across the top; every selected chip reveals its own `RepeatableInput` below, keyed by the option's `value`. Chips take a text `label`, an `icon`, or both (icon-only chips use `title` for the group heading and the accessible name). Rows are any input `type`, or a custom control via `renderRow` — that's exactly how the date picker's `mode="weekly"` builds its per-day time lists. `onChange` returns `Record<string, string[]>`; a key exists only while its chip is on. Pass `noneLabel` for an optional opt-out chip after the options — it and the real chips are mutually exclusive.

Pick a platform, then add every profile URL for it.

Icon-only chips — `title` names the group and labels the chip.

Autocomplete
Pass `autocomplete` with a flat `items` array (`string[]` or `{ label, value }[]`) to turn the input into a suggest-as-you-type field. It filters live, reveals every option on focus, and shows a clear button once there's a value.
Autocomplete with descriptions
Add a `description` to any option to render a secondary line under the label — useful for disambiguating similar entries.
Autocomplete with icons
Add an `icon` to any option to render a leading glyph next to the label.
API Reference
Props exposed by each input primitive.

TextField

PropTypeDefaultDescription
labelstringRenders an associated <Label> above the input.
captionstringHelper copy shown below the input. Hidden when `error` is set.
errorstringError message — toggles the red focus ring + red helper line.
requiredbooleanMarks the label with a red asterisk.
iconLeadingComponentType<{ className?: string }>Lucide icon component rendered inside the leading slot.
iconTrailingComponentType<{ className?: string }>Lucide icon component rendered inside the trailing slot.
type"text" | "email" | "tel" | "password" | "url" | "number" | "search" | …"text"Native HTML type. `email` and `tel` get blur-time validation hooks.
invalidMessagestringError message to surface when the built-in email/tel validator rejects the value.
onValidityChange(valid: boolean) => voidFires with the result of the email/tel validator on blur.

TextareaField

PropTypeDefaultDescription
labelstringRenders an associated <Label> above the textarea.
captionstringHelper copy shown below the textarea.
errorstringError message — toggles the red treatment.
rowsnumber3Initial visible row count.
showCharacterCountbooleanRender a live character count in the trailing slot.
characterCountnumberOverride the displayed character count.
trailingContentReactNodeCustom node rendered below-right (e.g. dictation button).

NumberInput

PropTypeDefaultDescription
valuenumberControlled value.
defaultValuenumber0Uncontrolled initial value. When value is 0 the input displays empty (placeholder shown).
onValueChange(value: number) => voidFires with the parsed numeric value on every change.
minnumberLower bound — decrement button disables when reached.
maxnumberUpper bound — increment button disables when reached.
stepnumber1Amount added/subtracted per stepper click.
placeholderstring"0"Placeholder shown when the value is 0 or empty.
captionReactNodeHelper copy shown below the input via FieldHint. Hidden when `error` is set.
errorboolean | stringError state. `true` applies the destructive ring only; a string also renders the inline message (`text-xs mt-1.5 text-red-500`) below the input.
disabledbooleanDims the field and prevents interaction.

FileSizeInput

PropTypeDefaultDescription
value{ width, height, unit }Controlled value. Width and height are number | "" — unit is one of in/cm/mm/px/pt.
onChange(value) => voidFires whenever any of the three sub-fields change.
allowedUnitsArray<"in" | "cm" | "mm" | "px" | "pt">Restrict the unit dropdown — defaults to all five.
idstringForwarded to the width input for label association.

TagsInput

PropTypeDefaultDescription
valuestring[]Controlled tag list.
onChange(value: string[]) => voidFires whenever a tag is added or removed.
maxnumberCap the chip count. Extras are silently rejected.
placeholderstringPlaceholder rendered inside the draft input.
captionstringHelper text below the field.

DurationInput

PropTypeDefaultDescription
unitsArray<"d" | "h" | "m" | "s">["h","m","s"]Which segments to render, left-to-right.
valuenumberControlled value in total seconds.
defaultValuenumber0Uncontrolled initial value in total seconds.
onValueChange(totalSeconds: number) => voidFires with the parsed total seconds on every change.
disabledbooleanDims the field and prevents interaction.

RepeatableInput

PropTypeDefaultDescription
valuestring[]Controlled list of row values. The component outputs `string[]`.
defaultValuestring[]Uncontrolled initial list. Padded with empty strings up to `min`.
onChange(value: string[]) => voidFires whenever a row is added, removed, or edited.
typeHTMLInputType"text"Input type for every row — forwarded to the underlying TextField.
placeholderstringPlaceholder applied to each row's input.
minnumber1Minimum number of rows. The remove button hides when at the floor.
maxnumberOptional maximum. The add-row link hides once reached.
addLabelstring"+ Add another"Text of the add-row link rendered under the list.
renderRow(row) => ReactNodeRender a custom control per row instead of the default TextField — receives `{ value, onChange, index, id, disabled, error }`. Add/remove behaviour is unchanged; the row hugs the custom control instead of stretching.
labelstringRenders the field label above the stack of inputs.
captionstringHelper text rendered directly under the label.
errorstringError message — toggles the red treatment on the first row.
requiredbooleanMarks the label with a red asterisk.
disabledbooleanDims every row and disables the +/− buttons.

GroupedRepeatableInput

PropTypeDefaultDescription
optionsGroupedRepeatableOption[]The chips, in render order — `{ value, label?, icon?, title?, disabled? }`. `value` keys the group's rows; `label` is the chip text (omit for an icon-only chip); `icon` renders before the label; `title` heads the group and names an icon-only chip.
valueRecord<string, string[]>Controlled value. A key exists only while its chip is on; toggling a chip off drops its rows.
defaultValueRecord<string, string[]>Uncontrolled initial value.
onChange(value: Record<string, string[]>) => voidFires on every chip toggle, row edit, add, and remove.
newRowValuestring""Row value seeded when a chip is switched on.
columns1 | 2 | 3 | 43Group columns at the widest breakpoint (always 1 on mobile).
noneLabelReactNodeRenders an optional opt-out chip after the options (“We're not online yet”). It and the real chips are mutually exclusive — picking it clears every group, picking a platform clears it. Omit to hide it.
noneIconComponentType<{ className?: string }>Icon rendered in the opt-out chip.
none / defaultNone / onNoneChangeboolean | (none: boolean) => voidControlled, uncontrolled, and change handler for the opt-out chip.
type / placeholder / iconLeading / min / max / addLabel / renderRowRepeatableInputPropsForwarded to every group's RepeatableInput — so the rows can be any input type, or a custom control via `renderRow`.
label / caption / error / required / disabledstring | booleanField chrome above the chips; `error` also reddens the groups.