

<Preview name="BasicInputExample" />

## Overview [#overview]

The `Input` component is a flexible form control for collecting user text input. It supports various input types, decorative elements like addons and icons, multiple sizes, and URL state synchronization for preserving input values across page navigation.

## Usage [#usage]

```tsx
import {
  Input,
  InputAddon,
  InputGroup,
  InputWrapper,
} from "@tilt-legal/cubitt-components/primitives";
```

```tsx
<Input type="email" placeholder="Email" />
```

<Accordions type="single">
  <Accordion title="URL State Management">
    Using Cubitt's URL-state hooks, you can sync the input value with the URL by providing a `paramName`:

    ```tsx
    // Value syncs with ?search=... in URL
    <Input
      type="text"
      placeholder="Search"
      paramName="search"
    />

    // Advanced options:
    <Input
      type="text"
      placeholder="Filter"
      paramName="filter"
      paramClearOnDefault={true}    // Remove param when empty
      paramDebounce={300}           // Debounce URL updates
      onUrlValueChange={(value) => console.log('Filter:', value)}
    />
    ```
  </Accordion>
</Accordions>

## Examples [#examples]

<Callout>
  Looking for date or time inputs? See the Date Input docs for `DateInput`,
  `DateInputRoot`, and `TimeInputRoot`.
</Callout>

### Basic Input [#basic-input]

A simple text input field.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="BasicInputExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Input } from "@tilt-legal/cubitt-components/primitives";

    <Input type="email" placeholder="Email" />;
    ```
  </Tab>
</Tabs>

### Disabled and Readonly [#disabled-and-readonly]

Input fields that are disabled or readonly.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DisabledReadonlyInputExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Input } from "@tilt-legal/cubitt-components/primitives";

    <Input type="text" placeholder="Disabled" disabled />

    <Input
      type="text"
      placeholder="Readonly"
      readOnly={true}
      value="Readonly input"
    />
    ```
  </Tab>
</Tabs>

### With Addons [#with-addons]

Use `InputGroup` with `InputAddon` to attach elements to an input. Each element maintains its own visual boundary.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="InputWithAddonsExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Button,
      Input,
      InputAddon,
      InputGroup,
    } from "@tilt-legal/cubitt-components/primitives";
    import { Input, InputWrapper } from "@tilt-legal/cubitt-components/primitives";
    import {
      CurrencyEuro } from "@tilt-legal/cubitt-icons/ui/outline";

    <InputGroup>
      <InputAddon>Text</InputAddon>
      <Input type="text" placeholder="Text addon" />
    </InputGroup>

    <InputGroup>
      <InputAddon>
        <CurrencyEuro />
      </InputAddon>
      <Input type="text" placeholder="Icon addon" />
    </InputGroup>

    <InputGroup>
      <Input type="text" placeholder="Button addon" />
      <Button variant="secondary" className="rounded-s-none rounded-e-xl">
        Submit
      </Button>
    </InputGroup>
    ```
  </Tab>
</Tabs>

### With Embedded Elements [#with-embedded-elements]

Use `InputWrapper` to place elements inside an input. Elements appear embedded within a single field.

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="InputWithIconsExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import {
      CurrencyEuro,
      Xmark } from "@tilt-legal/cubitt-icons/ui/outline";

    <InputWrapper>
      <Input type="text" placeholder="Text" />
      <span className="text-fg-3">@tilt.legal</span>
    </InputWrapper>

    <InputWrapper>
      <CurrencyEuro />
      <Input type="text" placeholder="Icon" />
    </InputWrapper>

    <InputWrapper>
      <Input type="text" placeholder="Button" />
      <Button size="sm" variant="link" mode="icon" className="size-5 -me-0.5">
        <Xmark />
      </Button>
    </InputWrapper>
    ```
  </Tab>
</Tabs>

### File Input [#file-input]

Input for file uploads.

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="FileInputExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Input } from "@tilt-legal/cubitt-components/primitives";

    <Input type="file" />;
    ```
  </Tab>
</Tabs>

### Copy to Clipboard [#copy-to-clipboard]

Input with a copy-to-clipboard button.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="CopyToClipboardInputExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      useRef } from "react";
    import { useBoolean, useCopyToClipboard, useDebounceCallback } from "@tilt-legal/cubitt-components/utilities/hooks";
    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import { Input, InputWrapper } from "@tilt-legal/cubitt-components/primitives";
    import {
      Tooltip,
      TooltipContent,
      TooltipProvider,
      TooltipTrigger,
    } from "@tilt-legal/cubitt-components/primitives";
    import {
      Check,
      Copy } from "@tilt-legal/cubitt-icons/ui/outline";

    export default function CopyToClipboardExample() {
      const { value: copied,
      setFalse,
      setTrue } = useBoolean(false);
      const [,
      copyToClipboard] = useCopyToClipboard();
      const resetCopied = useDebounceCallback(() => {
        setFalse();
      },
      2000);
      const inputRef = useRef<HTMLInputElement>(null);

      const handleCopy = async () => {
        if (!inputRef.current) {
          return;
        }

        const success = await copyToClipboard(inputRef.current.value);
        if (!success) {
          return;
        }

        setTrue();
        resetCopied();
      };

      return (
        <InputWrapper>
          <Input
            type="email"
            placeholder="Copy to clipboard"
            defaultValue="pnpm install @tilt-legal/cubitt-components"
            ref={inputRef}
          />
          <TooltipProvider delayDuration={0}>
            <Tooltip>
              <TooltipTrigger
                render={
                  <Button
                    onClick={handleCopy}
                    variant="link"
                    disabled={copied}
                    className="-me-3.5"
                  />
                }
              >
                {copied ? (
                  <Check className="stroke-green-600" size={16} />
                ) : (
                  <Copy size={16} />
                )}
              </TooltipTrigger>
              <TooltipContent className="px-2 py-1 text-xs">
                Copy to clipboard
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
        </InputWrapper>
      );
    }
    ```
  </Tab>
</Tabs>

### Clearable Input [#clearable-input]

Input with a clear button.

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="ClearableInputExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { useRef,
      useState } from "react";
    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import { Input, InputWrapper } from "@tilt-legal/cubitt-components/primitives";
    import {
      Xmark } from "@tilt-legal/cubitt-icons/ui/outline";

    export default function ClearableExample() {
      const [inputValue,
      setInputValue] = useState("Click to clear");
      const inputRef = useRef<HTMLInputElement>(null);

      const handleClearInput = () => {
        setInputValue("");
        if (inputRef.current) {
          inputRef.current.focus();
        }
      };

      return (
        <InputWrapper>
          <Input
            placeholder="Type some input"
            ref={inputRef}
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
          />
          {inputValue !== "" && (
            <Button
              onClick={handleClearInput}
              variant="link"
              className="-me-4"
              disabled={inputValue === ""}
            >
              <Xmark size={16} />
            </Button>
          )}
        </InputWrapper>
      );
    }
    ```
  </Tab>
</Tabs>

### Sizes [#sizes]

Input fields in different sizes.

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="InputSizesExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Input } from "@tilt-legal/cubitt-components/primitives";

    <Input type="text" size="sm" placeholder="Small" />
    <Input type="text" placeholder="Medium" />
    <Input type="text" size="lg" placeholder="Large" />
    ```
  </Tab>
</Tabs>

### Inline [#inline]

Use `variant="inline"` for inputs that look like text. Useful for editable titles, inline editing, or any context where you want the input to blend with surrounding text.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="InlineInputExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Input } from "@tilt-legal/cubitt-components/primitives";

    <Input
      variant="inline"
      className="text-2xl font-bold"
      defaultValue="Page title"
    />
    <p className="mt-2">
      This is an example of an inline input that looks like editable text.
    </p>
    ```
  </Tab>
</Tabs>

### URL State [#url-state]

Input with URL state synchronization.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="URLStateInputExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Input } from "@tilt-legal/cubitt-components/primitives";

    <Input
      type="text"
      placeholder="Type to update URL"
      paramName="search"
      defaultValue=""
    />;
    ```
  </Tab>
</Tabs>

***

## API Reference [#api-reference]

### Input [#input]

The main input component for text entry. Built on [Base UI Input](https://base-ui.com/react/components/input) with additional URL state management capabilities.

| Prop          | Type                    | Default     | Description                                         |
| ------------- | ----------------------- | ----------- | --------------------------------------------------- |
| `type`        | `string`                | `"text"`    | Input type (text, email, password, etc.)            |
| `variant`     | `"default" \| "inline"` | `"default"` | Visual style. Use `inline` for text-like inputs     |
| `size`        | `"sm" \| "md" \| "lg"`  | `"md"`      | Size of the input (ignored when `variant="inline"`) |
| `placeholder` | `string`                | -           | Placeholder text                                    |
| `disabled`    | `boolean`               | `false`     | Whether the input is disabled                       |
| `readOnly`    | `boolean`               | `false`     | Whether the input is read-only                      |
| `className`   | `string`                | -           | Additional CSS classes                              |

#### URL State Props [#url-state-props]

The following props enable URL state management by syncing the input value with URL parameters via TanStack Router search params. When `paramName` is provided, the input's value will be reflected in the URL.

| Prop                  | Type                              | Default | Description                                                                                 |
| --------------------- | --------------------------------- | ------- | ------------------------------------------------------------------------------------------- |
| `paramName`           | `string`                          | -       | URL parameter name for syncing state with URL. When provided, enables URL state management. |
| `paramValue`          | `string`                          | -       | Controlled value for the URL parameter (syncs with input `value`).                          |
| `onUrlValueChange`    | `(value: string \| null) => void` | -       | Callback when URL parameter value changes.                                                  |
| `paramClearOnDefault` | `boolean`                         | `true`  | Remove URL param when value equals default.                                                 |
| `paramThrottle`       | `number`                          | -       | Throttle URL updates in milliseconds.                                                       |
| `paramDebounce`       | `number`                          | -       | Debounce URL updates in milliseconds.                                                       |

### InputAddon [#inputaddon]

A decorative addon element for the input.

| Prop        | Type                   | Default | Description            |
| ----------- | ---------------------- | ------- | ---------------------- |
| `size`      | `"sm" \| "md" \| "lg"` | `"md"`  | Size to match input    |
| `className` | `string`               | -       | Additional CSS classes |

### InputGroup [#inputgroup]

Container for attaching `InputAddon` elements to an input. Each element maintains its own visual boundary.

| Prop        | Type     | Default | Description            |
| ----------- | -------- | ------- | ---------------------- |
| `className` | `string` | -       | Additional CSS classes |

### InputWrapper [#inputwrapper]

Container for placing icons or buttons inside an input. Elements appear embedded within a single field. Size is automatically detected from the `Input` inside.

| Prop        | Type     | Default | Description            |
| ----------- | -------- | ------- | ---------------------- |
| `className` | `string` | -       | Additional CSS classes |
