Text Input

A text input component supporting different visual variants, as well as leading and trailing icons. Built on the native <input> element for full form compatibility.

import { TextInput } from "@tenstorrent/vesper/text-input";

export default function TextInputDemo() {
  return (
    <TextInput aria-label="Email" placeholder="you@example.com" type="email" />
  );
}
Always give a TextInput an accessible name by either pairing it with a <label> via htmlFor or giving it an aria-label. Without one, assistive technology announces the field with no indication of what it is for.

Options

PropTypeDescriptionDefault
size"sm" | "md" | "lg"The size of the text input. Affects padding and typography."md"
variant"default" | "warning" | "success" | "error"The visual variant, which determines color scheme."default"
iconLeftReactNodeAn optional icon rendered to the left of the input field.—
iconLeftAction{ handler: (e: MouseEvent<HTMLButtonElement>) => void; ariaLabel: string }When provided, the left icon renders as a <button> that calls handler when clicked, labelled by ariaLabel.—
iconRightReactNodeAn optional icon rendered to the right of the input field.—
iconRightAction{ handler: (e: MouseEvent<HTMLButtonElement>) => void; ariaLabel: string }When provided, the right icon renders as a <button> that calls handler when clicked, labelled by ariaLabel.—
type"text" | "email" | "password" | "url" | "tel" | "search" | "number" | "date" | "datetime-local" | "week" | "month" | "time"The HTML input type."text"
refRef<HTMLInputElement>A ref forwarded to the underlying <input> element.—
placeholderstringPlaceholder text for the input." "
disabledbooleanWhen true, prevents interaction.false
requiredbooleanWhen true, marks the input as required.false
readOnlybooleanWhen true, makes the input read-only.false

Form props (value, disabled, name, form, etc.), input props (autoCorrect, spellCheck, autoFocus, etc.), aria-* attributes, ref, and event handlers related to focus, blur, scroll, input, and change events are forwarded to the underlying <input> element. All other props are forwarded to the wrapping <div> element.

Examples

Uncontrolled vs controlled

Render a TextInput in an uncontrolled fashion to let it keep track of its own value:

import { TextInput } from "@tenstorrent/vesper/text-input";

export default function UncontrolledTextInput() {
  return <TextInput aria-label="About" placeholder="Enter some text" />;
}

If you need to control the value of the TextInput, you can do so via the value and onChange props:

import { useState } from "react";

import { TextInput } from "@tenstorrent/vesper/text-input";

export default function ControlledTextInput() {
  const [value, setValue] = useState("");

  return (
    <TextInput
      aria-label="About"
      placeholder="Enter some text"
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

Different sizes

The TextInput component can be rendered at sm, md, or lg size, defaulting to md. Size affects the padding and text styles of the input, as well as the size of the rendered icons.

import { TextInput } from "@tenstorrent/vesper/text-input";

export default function TextInputSizes() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "var(--vesper-spacing-4)",
      }}
    >
      <TextInput
        aria-label="Username"
        size="sm"
        placeholder="A small text input"
      />
      <TextInput
        aria-label="Username"
        size="md"
        placeholder="A medium text input"
      />
      <TextInput
        aria-label="Username"
        size="lg"
        placeholder="A large text input"
      />
    </div>
  );
}

Rendering icons

You can render icons to the left or the right of the input via the iconLeft and iconRight props:

import { Globe, Search } from "@tenstorrent/vesper/icons";
import { TextInput } from "@tenstorrent/vesper/text-input";

export default function TextInputWithIcons() {
  return (
    <TextInput
      aria-label="Search"
      placeholder="Enter some text"
      iconLeft={<Globe />}
      iconRight={<Search />}
    />
  );
}

Rendered icons can be treated as buttons by passing the iconLeftAction and iconRightAction props. Each action takes a handler that is called when the icon is clicked, and an ariaLabel that gives the resulting button an accessible label:

import { Search } from "@tenstorrent/vesper/icons";
import { TextInput } from "@tenstorrent/vesper/text-input";

export default function TextInputWithIconAction() {
  return (
    <TextInput
      aria-label="Search term"
      placeholder="Enter some text"
      iconLeft={<Search />}
      iconLeftAction={{
        handler: () => console.log("Search logs"),
        ariaLabel: "Search logs",
      }}
    />
  );
}

Disabling the input

You can disable a TextInput by passing disabled={true} or just disabled as a prop:

import { TextInput } from "@tenstorrent/vesper/text-input";

export default function DisabledTextInput() {
  return (
    <TextInput
      aria-label="Byline"
      placeholder="Who wrote this article?"
      disabled
    />
  );
}

Variants

The TextInput component can be rendered in one of four variants: default, warning, success, or error. The variant determines the colour scheme of the input.

Default variant

A TextInput renders with the default variant when no variant prop is specified:

import { TextInput } from "@tenstorrent/vesper/text-input";

export default function DefaultTextInput() {
  return (
    <TextInput
      aria-label="Username"
      placeholder="Enter your desired username"
    />
  );
}

Warning variant

Use the warning variant to flag a value that needs attention, but that doesn't prevent the form from being submitted:

import { TextInput } from "@tenstorrent/vesper/text-input";

export default function WarningTextInput() {
  return (
    <TextInput
      aria-label="Password"
      placeholder="Enter your password"
      variant="warning"
      type="password"
      defaultValue="password1"
    />
  );
}

Success variant

The success variant is best suited for confirming that a value has been validated as expected:

import { useState } from "react";

import { TextInput } from "@tenstorrent/vesper/text-input";

export default function SuccessTextInput() {
  const [workspace, setWorkspace] = useState("vesper");

  return (
    <TextInput
      aria-label="Workspace"
      placeholder="Enter the name of your workspace"
      variant="success"
      value={workspace}
      onChange={(e) => setWorkspace(e.target.value)}
    />
  );
}

Error variant

The error variant highlights an invalid value that must be corrected before the form can be submitted:

import { useState } from "react";

import { TextInput } from "@tenstorrent/vesper/text-input";

export default function ErrorTextInput() {
  const [email, setEmail] = useState("you@example.com");

  return (
    <TextInput
      aria-label="Email"
      placeholder="Enter your email"
      variant="error"
      type="email"
      value={email}
      onChange={(e) => setEmail(e.target.value)}
      required
    />
  );
}

Usage in forms

TextInput renders a native <input>, so it works with regular form submission and native validation via props like name, form, required, pattern, minLength, and maxLength:

import { Button } from "@tenstorrent/vesper/button";
import { TextInput } from "@tenstorrent/vesper/text-input";

export default function ContactForm() {
  return (
    <form action="/api/contact" method="post">
      <TextInput
        aria-label="Phone number"
        name="phone"
        type="tel"
        placeholder="555-555-5555"
        pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
        required
      />
      <Button type="submit">Submit</Button>
    </form>
  );
}

Accessing the underlying element

Use the ref prop when you need direct access to the underlying <input> element, eg. to focus it or read its selection:

import { useRef } from "react";

import { Search } from "@tenstorrent/vesper/icons";
import { TextInput } from "@tenstorrent/vesper/text-input";

export default function SearchField() {
  const ref = useRef<HTMLInputElement>(null);

  return (
    <TextInput
      aria-label="Search term"
      placeholder="Search logs"
      ref={ref}
      iconLeft={<Search />}
      iconLeftAction={{
        handler: () => ref.current?.focus(),
        ariaLabel: "Search logs",
      }}
    />
  );
}