---
layout: ~/layouts/MdxDocLayout.astro
title: "Input"
description: "A text input field for user input with built-in label, description, and error support."
sourceFile: "components/input"
baseUIComponent: "input"
---

import ComponentExample from "~/components/docs/ComponentExample.astro";
import ComponentSection from "~/components/docs/ComponentSection.astro";
import CodeBlock from "~/components/docs/CodeBlock.astro";
import Heading from "~/components/docs/Heading.astro";
import PropsTable from "~/components/docs/PropsTable.astro";
import {
  InputBasicDemo,
  InputWithLabelDemo,
  InputErrorStringDemo,
  InputErrorObjectDemo,
  InputSizesDemo,
  InputDisabledDemo,
  InputBareDemo,
  InputTypesDemo,
  InputOptionalFieldDemo,
  InputLabelTooltipDemo,
  InputReactNodeLabelDemo,
} from "~/components/demos/InputDemo";

<ComponentSection>
  <ComponentExample
    code={`<Input 
  label="Email" 
  placeholder="you@example.com"
  description="We'll never share your email"
/>`}
  >
    <InputBasicDemo client:visible />
  </ComponentExample>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Installation</Heading>
  <Heading level={3}>Barrel</Heading>
  <CodeBlock code={`import { Input } from "@cloudflare/kumo";`} lang="tsx" />
  <Heading level={3}>Granular</Heading>
  <CodeBlock
    code={`import { Input } from "@cloudflare/kumo/components/input";`}
    lang="tsx"
  />
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Usage</Heading>
  <Heading level={3}>With Built-in Field (Recommended)</Heading>
  <p>
    Use the `label` prop
    to enable the built-in Field wrapper with label, description, and error support.
  </p>
  <CodeBlock
    code={`import { Input } from "@cloudflare/kumo";

export default function Example() {
  return (
    <Input 
      label="Email" 
      placeholder="you@example.com"
      description="We'll never share your email"
    />
  );
}`}
    lang="tsx"
  />

  <Heading level={3}>Bare Input (Custom Layouts)</Heading>
  <p>
    For custom form layouts, use Input without `label`. Must provide `aria-label` or `aria-labelledby` for accessibility.
  </p>
  <CodeBlock
    code={`import { Input } from "@cloudflare/kumo";

export default function Example() {
  return <Input placeholder="Search..." aria-label="Search products" />;
}`}
    lang="tsx"
  />
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Examples</Heading>

<Heading level={3}>With Label and Description</Heading>
<p>
  The `label`
  prop enables the built-in Field wrapper with automatic vertical layout (label
  above input).
</p>
<ComponentExample
  code={`<Input 
  label="Username" 
  placeholder="Choose a username"
  description="3-20 characters, alphanumeric only"
/>`}
>
  <InputWithLabelDemo client:visible />
</ComponentExample>

  <Heading level={3}>With Error (String)</Heading>
  <p>
    Pass `error` as
    a string for simple error messages. Error replaces description when present.
  </p>
  <ComponentExample
    code={`<Input

label="Email"
placeholder="you@example.com"
value="invalid-email"
variant="error"
error="Please enter a valid email address"
/>`} >

<InputErrorStringDemo client:visible />
</ComponentExample>

  <Heading level={3}>With Error (Validation Object)</Heading>
  <p>
    Pass `error` as
    an object with `message` and `match` for
    HTML5 validation. Error shows when field validity matches.
  </p>
  <ComponentExample
    code={`<Input

label="Password"
type="password"
value="short"
variant="error"
error={{
    message: "Password must be at least 8 characters",
    match: "tooShort"
  }}
minLength={8}
/>`} >

<InputErrorObjectDemo client:visible />
</ComponentExample>

  <Heading level={3}>Input Sizes</Heading>
  <p>
    Four sizes available: `xs`, `sm`, `base` (default), `lg`.
  </p>
  <ComponentExample
    code={`<>

  <Input size="xs" label="Extra Small" placeholder="Extra small input" />
  <Input size="sm" label="Small" placeholder="Small input" />
  <Input label="Base" placeholder="Base input (default)" />
  <Input size="lg" label="Large" placeholder="Large input" />
</>`}
  >
    <InputSizesDemo client:visible />
  </ComponentExample>

  <Heading level={3}>Disabled</Heading>
  <ComponentExample
    code={`<Input

label="Disabled field"
placeholder="Cannot edit"
disabled
/>`} >

<InputDisabledDemo client:visible />
</ComponentExample>

  <Heading level={3}>Optional Field</Heading>
  <p>
    Set `required={"{false}"}` to show "(optional)" text after the label.
  </p>
  <ComponentExample
    code={`<Input

label="Phone Number"
required={false}
placeholder="+1 (555) 000-0000"
/>`} >

<InputOptionalFieldDemo client:visible />
</ComponentExample>

  <Heading level={3}>With Label Tooltip</Heading>
  <p>
    Use `labelTooltip` to add an info icon with additional context on hover.
  </p>
  <ComponentExample
    code={`<Input

label="API Key"
labelTooltip="Find this in your dashboard under Settings > API Keys"
placeholder="sk*live*..."
/>`} >

<InputLabelTooltipDemo client:visible />
</ComponentExample>

  <Heading level={3}>ReactNode Label</Heading>
  <p>
    The `label`
    prop accepts ReactNode for rich formatting.
  </p>
  <ComponentExample
    code={`<Input

label={<span>Email for <strong>billing</strong></span>}
required
placeholder="billing@company.com"
type="email"
/>`} >

<InputReactNodeLabelDemo client:visible />
</ComponentExample>

<Heading level={3}>Bare Input (No Label)</Heading>
<p>
  Input without
  `label` renders
  as a bare input. Must provide
  `aria-label`
  for accessibility.
</p>
<ComponentExample
  code={`<Input placeholder="Search..." aria-label="Search products" />`}
>
  <InputBareDemo client:visible />
</ComponentExample>

  <Heading level={3}>Input Types</Heading>
  <p>
    Supports all HTML input types: `text`, `email`, `password`, `number`, `tel`, `url`, etc.
  </p>
  <ComponentExample
    code={`<>

  <Input type="email" label="Email" placeholder="you@example.com" />
  <Input type="password" label="Password" placeholder="••••••••" />
  <Input type="number" label="Age" placeholder="18" />
  <Input type="tel" label="Phone" placeholder="+1 (555) 000-0000" />
</>`}
  >
    <InputTypesDemo client:visible />
  </ComponentExample>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>API Reference</Heading>
  <p>
    Input accepts all standard HTML input attributes plus the following:
  </p>
  <PropsTable component="Input" />

  <Heading level={3}>Validation Error Types</Heading>
  <p>
    When using `error` as
    an object, the `match`
    property corresponds to HTML5 ValidityState values:
  </p>
  <div class="overflow-x-auto">
    <table class="w-full text-sm">
      <thead>
        <tr class="border-b border-kumo-line">
          <th class="px-4 py-3 text-left font-semibold">Match</th>
          <th class="px-4 py-3 text-left font-semibold">Description</th>
        </tr>
      </thead>
      <tbody class="text-kumo-strong">
        <tr class="border-b border-kumo-line">
          <td class="px-4 py-3 font-mono text-xs">valueMissing</td>
          <td class="px-4 py-3 text-xs">Required field is empty</td>
        </tr>
        <tr class="border-b border-kumo-line">
          <td class="px-4 py-3 font-mono text-xs">typeMismatch</td>
          <td class="px-4 py-3 text-xs">Value doesn't match type (e.g., invalid email)</td>
        </tr>
        <tr class="border-b border-kumo-line">
          <td class="px-4 py-3 font-mono text-xs">patternMismatch</td>
          <td class="px-4 py-3 text-xs">Value doesn't match pattern attribute</td>
        </tr>
        <tr class="border-b border-kumo-line">
          <td class="px-4 py-3 font-mono text-xs">tooShort</td>
          <td class="px-4 py-3 text-xs">Value shorter than minLength</td>
        </tr>
        <tr class="border-b border-kumo-line">
          <td class="px-4 py-3 font-mono text-xs">tooLong</td>
          <td class="px-4 py-3 text-xs">Value longer than maxLength</td>
        </tr>
        <tr class="border-b border-kumo-line">
          <td class="px-4 py-3 font-mono text-xs">rangeUnderflow</td>
          <td class="px-4 py-3 text-xs">Value less than min</td>
        </tr>
        <tr class="border-b border-kumo-line">
          <td class="px-4 py-3 font-mono text-xs">rangeOverflow</td>
          <td class="px-4 py-3 text-xs">Value greater than max</td>
        </tr>
        <tr class="border-b border-kumo-line">
          <td class="px-4 py-3 font-mono text-xs">true</td>
          <td class="px-4 py-3 text-xs">Always show error (for server-side validation)</td>
        </tr>
      </tbody>
    </table>
  </div>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Accessibility</Heading>
  <div class="space-y-4 text-sm">
    <div>
      <h3 class="mb-2 font-semibold">Label Requirement</h3>
      <p class="text-kumo-strong">
        Inputs require an accessible name via one of:
      </p>
      <ul class="mt-2 ml-4 list-disc space-y-1 text-kumo-strong">
        <li>
          `label`
          prop (recommended)
        </li>
        <li>
          `placeholder`
          +
          `aria-label`
          for bare inputs
        </li>
        <li>
          `aria-labelledby`
          for custom label association
        </li>
      </ul>
      <p class="mt-2 text-kumo-strong">
        Missing accessible names trigger console warnings in development.
      </p>
    </div>
    <div>
      <h3 class="mb-2 font-semibold">Error Association</h3>
      <p class="text-kumo-strong">
        Error messages are automatically associated with the input via ARIA
        attributes for screen reader announcement.
      </p>
    </div>
  </div>
</ComponentSection>
