Meowland docs
Workbench

Component reference

Input

A single-line text input.

Preview

Control centers and symmetry

Checks the vertical center and top/bottom symmetry of single-line inputs, selects, button icons/text, and the inline composer on one screen; shared by visual review and geometry regressions.

packages/meowtail/stories/basic-quality.stories.tsxline 42tsx
render() {
  const choice = signal("current");
  const rowChoice = signal("current");
  const view = signal("list");
  return (
    <div className="workshop-stack" data-basic-quality-scene>
      <Specimen label="Single-line inputs and selects" align="start">
        <Input aria-label="Project name" value="Workspace" data-batch1="input" />
        <Input
          aria-label="Project name placeholder"
          placeholder="Project name"
          data-batch1="input-placeholder"
        />
        <Select aria-label="Current workspace" value="current" data-batch1="select">
          <option value="current">Current workspace</option>
          <option value="new">New workspace</option>
        </Select>
        <ChoiceSelect
          id="basic-quality-choice"
          aria-label="Select model"
          value={choice}
          onValueChange={(value) => {
            choice.value = value;
          }}
          data-batch1="choice-select"
          options={[
            { value: "current", label: "DeepSeek-V4-Pro" },
            { value: "fast", label: "DeepSeek-V4-Fast" },
          ]}
        />
      </Specimen>

      <Specimen label="One row of every single-line control" align="start">
        {/* Top-aligned on purpose: the row only lines up if every height agrees. */}
        <Inline gap="compact" align="start" data-mixed-row>
          <Input aria-label="Filter name" placeholder="Name" />
          <SearchInput label="Search files" placeholder="Search" />
          <Select aria-label="Status">
            <option>Any status</option>
          </Select>
          <ChoiceSelect
            id="basic-quality-row-choice"
            aria-label="Owner"
            value={rowChoice}
            onValueChange={(value) => {
              rowChoice.value = value;
            }}
            options={[
              { value: "current", label: "Anyone" },
              { value: "me", label: "Me" },
            ]}
          />
          <NumberInput defaultValue={10} aria-label="Limit" />
          <SegmentedControl
            id="basic-quality-row-view"
            label="View"
            value={view}
            onValueChange={(value) => {
              view.value = value;
            }}
            options={[
              { value: "list", label: "List" },
              { value: "grid", label: "Grid" },
            ]}
          />
          <Button variant="outline">Reset</Button>
          <Button>Apply</Button>
          <IconButton label="Refresh">
            <Icon name="refresh-cw" />
          </IconButton>
        </Inline>
      </Specimen>

      <Specimen label="Actions" align="start">
        <div className="workshop-row">
          <Button data-batch1="button-text">Save changes</Button>
          <Button data-batch1="button-icon-text">
            <Icon name="search" />
            Search
          </Button>
          <IconButton label="Search" data-batch1="icon-button">
            <Icon name="search" />
          </IconButton>
        </div>
      </Specimen>

      <Specimen label="Switches and checkboxes" align="start">
        <div className="workshop-row">
          <Checkbox label="Keep draft" defaultChecked data-batch1="checkbox" />
          <Switch label="Notify when complete" data-batch1="switch" />
        </div>
      </Specimen>

      <Specimen label="Inline input" align="start">
        <div style={{ width: "100%" }}>
          <ChatComposer
            layout="inline"
            placeholder="Write a message…"
            inputLabel="Message"
            submitLabel="Send"
            onSubmit={(event) => event.preventDefault()}
            data-batch1="composer"
          />
        </div>
      </Specimen>
    </div>
  );
}

Installation

bun add meowtail

Usage

Taken from the examples on this page.

usage.tsxtsx
import { Input } from "meowtail";

<Input aria-label="Project name" value="Workspace" />

Examples

The other stories that show this component.

Normal and long labels

Native input behavior and Field label association; long labels, helper descriptions, and multi-line content are shown together.

packages/meowtail/stories/foundation.stories.tsxline 169tsx
render: () => {
  const delivery = signal("review");
  return (
    <form className="workshop-stack workshop-constrained" data-project-form novalidate>
      <Field
        label="Project name"
        htmlFor="workshop-project"
        description="Local demo only; nothing is saved to a service."
      >
        <Input id="workshop-project" name="project" value="Meowland" required />
      </Field>
      <Field
        label="Delivery method"
        htmlFor="workshop-delivery"
        description="Review the diff first, then decide whether to submit."
      >
        <Select
          id="workshop-delivery"
          name="delivery"
          aria-describedby="workshop-delivery-description"
          value={delivery}
          required
          onChange={(event) => {
            delivery.value = (event.currentTarget as HTMLSelectElement).value;
          }}
        >
          <option value="draft">Keep draft</option>
          <option value="review">Review the diff first</option>
          <option value="export">Export results</option>
        </Select>
        <div className="workshop-foundation-field-action">
          <span>Not sure which to pick?</span>
          <Button
            variant="quiet"
            size="small"
            onClick={() => {
              delivery.value = "review";
            }}
          >
            Use recommended settings
          </Button>
        </div>
      </Field>
      <Field
        label="Add constraints this work must follow and how you want the results checked when done"
        htmlFor="workshop-notes"
      >
        <Textarea
          id="workshop-notes"
          rows={4}
          placeholder="Write down constraints, or paste longer content…"
        />
      </Field>
      <div className="workshop-row">
        <Button type="submit">Save project</Button>
        <Button type="reset" variant="quiet">
          Restore initial values
        </Button>
      </div>
      <Note id="project-feedback" role="status">
        Change the name and save; leave it empty to see validation feedback.
      </Note>
    </form>
  );
}

Error and disabled

The error description is associated with the control by a real Field; a disabled input stays readable. This sample uses a fixed error state.

packages/meowtail/stories/foundation.stories.tsxline 278tsx
render: () => (
  <div className="workshop-stack workshop-constrained">
    <Field
      label="Project name"
      htmlFor="workshop-invalid"
      error="Enter a project name; spaces alone are not enough."
    >
      <Input id="workshop-invalid" value="" />
    </Field>
    <Field
      label="Working directory"
      htmlFor="workshop-disabled"
      description="The directory cannot be changed while running."
    >
      <Input id="workshop-disabled" value="/workspace/meowland" disabled />
    </Field>
    <Field
      label="Runtime environment"
      htmlFor="workshop-disabled-select"
      description="Cannot be switched while work is in progress."
    >
      <Select id="workshop-disabled-select" disabled>
        <option>Local workspace</option>
      </Select>
    </Field>
    <Field
      label="Additional notes"
      htmlFor="workshop-invalid-notes"
      error="The description exceeds the length allowed by this sample; shorten it and continue."
    >
      <Textarea id="workshop-invalid-notes" rows={3}>
        A longer description that needs to be added.
      </Textarea>
    </Field>
  </div>
)

Read-only, errors and long content

Read-only content can still be selected and copied; disabled content cannot be edited. Explicit errors, composed fields and long content keep a clear association.

packages/meowtail/stories/input-states.stories.tsxline 21tsx
render: () => (
  <div className="workshop-composition-columns">
    <section className="workshop-stack">
      <Field
        label="Project path (read-only)"
        htmlFor="readonly-path"
        description="Can be selected and copied; it cannot be changed here."
      >
        <Input
          id="readonly-path"
          readonly
          value="/workspace/meowland/packages/meowtail/components/long-project-reference"
        />
      </Field>
      <Field label="Search files" htmlFor="search-files">
        <Input id="search-files" type="search" placeholder="Enter a file name…" />
      </Field>
      <Field
        label="Notification email"
        htmlFor="invalid-email"
        invalid
        error="Enter a complete email address."
      >
        <Input id="invalid-email" type="email" value="meow@" />
      </Field>
      <Field label="Access passphrase (disabled)" htmlFor="disabled-password">
        <Input id="disabled-password" type="password" value="demo-only" disabled />
      </Field>
      <Field
        label="Check range"
        description="For composed fields, the caller associates the label and description separately."
        descriptionId="range-description"
      >
        <div className="workshop-foundation-range">
          <label>
            From{" "}
            <Input
              aria-label="Start line"
              aria-describedby="range-description"
              type="number"
              value="1"
              min="1"
            />
          </label>
          <label>
            To{" "}
            <Input
              aria-label="End line"
              aria-describedby="range-description"
              type="number"
              value="80"
              min="1"
            />
          </label>
        </div>
      </Field>
    </section>
    <section className="workshop-stack">
      <Field label="Delivery notes (editable)" htmlFor="long-notes">
        <Textarea id="long-notes" rows={6}>
          First, confirm every component stays readable on narrow screens. Second, keep user input
          while switching themes. Third, open a dialog with the keyboard, cancel it and return.
          Long content should wrap naturally; never drop the evidence for checks to keep the
          screen tidy.
        </Textarea>
      </Field>
      <Field label="Last check record (read-only)" htmlFor="readonly-notes">
        <Textarea id="readonly-notes" rows={3} readonly>
          The record is kept for checking. Read-only does not mean inaccessible.
        </Textarea>
      </Field>
      <Field label="Waiting for the connection to return" htmlFor="disabled-notes">
        <Textarea id="disabled-notes" rows={3} disabled>
          Continue editing once the connection returns.
        </Textarea>
      </Field>
    </section>
  </div>
)

API reference

Props

PropTypeDefault
type
string"text"

Also takes className, children and the attributes and events of its root element.

DOM parts

root

Registry

The component is registered as input in the meowtail component registry.