> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Text Field – Code

> Text Fields are form elements that allow users to enter custom strings of text.

export const LiveCode = ({children, customHeight, clickToLoad, example, fullWidth, fullHeight, hideCodeInLiveCode, screenshot, screenshotOnly, showCode: showCodeProp}) => {
  const SCREENSHOTS_BASE = "https://servicetitan.github.io/anvil2-docs-live-code/screenshots";
  const STACKBLITZ_BASE = "https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples";
  const [showCodeBlock, setShowCodeBlock] = useState(showCodeProp ?? false);
  const [isLocalOverride, setIsLocalOverride] = useState(false);
  useEffect(() => {
    const examplePath = `/images/live-code-screenshots-tmp/${example}.png`;
    fetch(examplePath, {
      method: "HEAD"
    }).then(r => {
      if (r.ok) setIsLocalOverride(true);
    }).catch(() => {});
  }, [example]);
  const screenshotBase = isLocalOverride ? "/images/live-code-screenshots-tmp" : SCREENSHOTS_BASE;
  if (screenshotOnly) {
    return <Frame className="flex flex-col">
        <div className="flex dark:hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#FFFFFF"
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>
        <div className="hidden dark:flex" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#141414"
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>
      </Frame>;
  }
  if (screenshot) {
    return <Frame className="flex flex-col -mb-2">
        <div className="flex dark:hidden bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>

        <div className="hidden dark:flex bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      background: "#141414",
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>

        <div className="flex justify-end items-center text-xs py-2 px-1 gap-4">
          {!showCodeProp ? <button className="inline-flex justify-end items-center text-gray-700 dark:text-gray-50 hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1 cursor-pointer" onClick={() => setShowCodeBlock(!showCodeBlock)} style={{
      appearance: "none"
    }}>
              <Icon icon="code" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
              <span>{showCodeBlock ? "Hide code" : "Show code"}</span>
            </button> : null}

          <a className="inline-flex justify-end items-center hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1" href={`${STACKBLITZ_BASE}/${example}?file=src/App.tsx`} target="_blank" rel="noreferrer">
            <Icon icon="bolt" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
            <span>StackBlitz demo</span>
          </a>
        </div>

        <div className="grid transition-[grid-template-rows] duration-300 ease-in-out overflow-auto overflow-y-hidden overflow-x-auto" style={showCodeBlock ? {
      gridTemplateRows: "1fr"
    } : {
      gridTemplateRows: "0fr"
    }}>
          <div style={{
      minHeight: 0,
      overflowX: "auto",
      overflowY: "hidden",
      marginBlockStart: "-1.25rem",
      marginBlockEnd: "-1.5rem"
    }}>
            {children}
          </div>
        </div>
      </Frame>;
  } else {
    return <div style={{
      display: "flex",
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px",
      resize: "vertical",
      overflow: "auto"
    }}>
        <iframe title={example} style={{
      flex: 1,
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px"
    }} src={`${STACKBLITZ_BASE}/${example}?embed=1&hideNavigation=1&hideExplorer=1&terminalHeight=0&file=src/App.tsx${clickToLoad ? "&ctl=1" : ""}${hideCodeInLiveCode ? "&view=preview" : ""}`} allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />
      </div>;
  }
};

<Tabs>
  <Tab title="Implementation">
    <LiveCode showCode example="textfield-playground" fullWidth screenshot>
      ```tsx lines expandable theme={null}
      import { TextField } from "@servicetitan/anvil2";

      function App() {
        return (
          <TextField
            label="Label"
            placeholder="Placeholder"
            hint="Hint text"
            moreInfo="More info"
            size="large"
            required
            loading
            error={false}
          />
        );
      }

      export default App;
      ```
    </LiveCode>

    ## Common Examples

    ```tsx theme={null}
    import { TextField } from "@servicetitan/anvil2";

    function ExampleComponent() {
      return (
        <TextField
          label="Label"
          placeholder="Placeholder"
          hint="Hint text"
          moreInfo="More info"
          required
          error={false}
        />
      );
    }
    ```

    The `TextField` component can be used in the same way that you would use a pairing of HTML `input` and `label` elements.

    ### Managing text field state

    There are many ways to handle form element values and state. The Anvil2 `TextField` component is very flexible and can be implemented as part of a `form`, using React `ref`, or by manually controlling state.

    <LiveCode showCode example="textfield-uncontrolled" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { TextField, Flex, Button } from "@servicetitan/anvil2";

      function App() {
        return (
          <form
            onSubmit={(e) => {
              e.preventDefault();
              console.log(
                ((e.target as HTMLFormElement)[0] as HTMLInputElement).value,
              );
            }}
          >
            <Flex direction="column" gap={4}>
              <TextField
                name="example-text-field"
                label="Example text field"
                hint="Fill this out, press submit, and look in the console."
              />
              <Button type="submit" appearance="primary">
                Submit
              </Button>
            </Flex>
          </form>
        );
      }

      export default App;
      ```
    </LiveCode>

    #### Text fields in an uncontrolled form

    Use a `TextField` in the same manner as an HTML `input` element with an associated `label`. The Anvil2 [Button](/docs/web/components/button/design) can be used with `type="submit"` to submit all of the form input values.

    <LiveCode showCode example="textfield-controlled" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { TextField, Flex, Button } from "@servicetitan/anvil2";
      import { useState } from "react";

      function App() {
        const [value, setValue] = useState("");

        return (
          <form
            onSubmit={(e) => {
              e.preventDefault();
              console.log(value);
            }}
          >
            <Flex direction="column" gap={4}>
              <TextField
                name="example-text-field"
                label="Example text field"
                hint="Fill this out, press submit, and look in the console."
                value={value}
                onChange={(e) => setValue(e.target.value)}
              />
              <Button type="submit" appearance="primary">
                Submit
              </Button>
            </Flex>
          </form>
        );
      }

      export default App;
      ```
    </LiveCode>

    #### Controlled text fields

    To manually control the state of a text field, use the `value` and `onChange` props along with a React state (or similar state management tool).

    ### Show character counter

    Use the `maxLength` and `showCounter` props to display a character counter below the text field.

    <LiveCode showCode example="textfield-showcounter" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { TextField } from "@servicetitan/anvil2";

      function App() {
        return (
          <TextField
            label="Label"
            placeholder="Placeholder"
            maxLength={180}
            showCounter
          />
        );
      }

      export default App;
      ```
    </LiveCode>

    ### Markdown in labels

    The `label` prop supports inline markdown: bold (`**text**`), italic (`*text*`), bold and italic (`***text***`), highlight (`==text==`), and code (`` `text` ``).

    <LiveCode showCode example="textfield-markdownlabel" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { Flex, TextField } from "@servicetitan/anvil2";

      function App() {
        return (
          <Flex direction="column" gap="4" style={{ maxWidth: 400 }}>
            <TextField label="**Bold** label" placeholder="Bold" />
            <TextField label="*Italic* label" placeholder="Italic" />
            <TextField
              label="***Bold and italic*** label"
              placeholder="Bold and italic"
            />
            <TextField label="==Highlight== label" placeholder="Highlight" />
            <TextField label="`Code` label" placeholder="Code" />
          </Flex>
        );
      }

      export default App;
      ```
    </LiveCode>

    ### Hide the label

    Use `hideLabel` to visually hide the label. The `label` string is converted to an `aria-label` on the input so it remains accessible to screen readers — any inline markdown is stripped to plain text.

    <LiveCode showCode example="textfield-hidelabel" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { Flex, TextField } from "@servicetitan/anvil2";

      function App() {
        return (
          <Flex direction="row" gap="2" style={{ maxWidth: 500 }}>
            <TextField label="First name" hideLabel placeholder="First name" />
            <TextField label="Last name" hideLabel placeholder="Last name" />
          </Flex>
        );
      }

      export default App;
      ```
    </LiveCode>
  </Tab>

  <Tab title="TextField Props">
    ```tsx theme={null}
    <TextField
      label="Label"
      description="Description"
      hint="Hint text"
      error="Error message"
      errorAriaLive="assertive"
      loading={false}
      moreInfo="More info"
      prefix="Prefix"
      required={false}
      size="medium"
      suffix="Suffix"
      type="text"
      showCounter={true}
      maxLength={100}
      placeholder="Placeholder"
    />
    ```

    ## `TextField` Props

    The `TextField` component spreads any props not listed here to the underlying HTML `input` element, so props such as id and name will work as expected. However, the `type` prop is restricted to the values listed below. Note that there is no prop to control the width: the top-level element of the `TextField` is set to `display: flex` and it will expand to fill the width of its parent element.

    <ParamField path="description" type="ReactElement | string" />

    <ParamField path="disabled" type="boolean" default="false" />

    <ParamField path="error" type="ReactElement | string | boolean">
      If `true`, the `TextField` will show an error state with no message. Any
      non-boolean value will also display as an error message.
    </ParamField>

    <ParamField path="errorAriaLive" type={`"off" | "assertive" | "polite"`} default="assertive">
      See [mdn
      docs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live).
    </ParamField>

    <ParamField path="hideLabel" type="boolean" default="false">
      Visually hides the label while keeping it accessible to screen readers via `aria-label`.
    </ParamField>

    <ParamField path="hint" type="ReactNode" />

    <ParamField path="label" type="ReactNode">
      Label for the field. Supports inline markdown formatting.

      <Warning>Omitting `label` is deprecated and will be required in v4.0.0. Use `hideLabel` to visually hide it. Passing `ReactNode` is also deprecated — use a plain string with inline markdown instead.</Warning>
    </ParamField>

    <ParamField path="loading" type="boolean">
      Loading spinner is rendered when `true`
    </ParamField>

    <ParamField path="maxLength" type="number">
      Required if `showCounter` is `true`.
    </ParamField>

    <ParamField path="moreInfo" type="string" />

    <ParamField path="prefix" type="string" />

    <ParamField path="required" type="boolean" default="false" />

    <ParamField path="size" type={`"small" | "medium" | "large"`} default="medium" />

    <ParamField path="showCounter" type="boolean" />

    <ParamField path="suffix" type="string | ReactElement" />

    <ParamField path="type" type={`"text" | "email" | "tel" | "url" | "password" | "number"`} default="text" />
  </Tab>
</Tabs>
