# Input

> Text input with three variants, label tooltips, validation state, password toggle, copy-to-clipboard, character count, and a built-in formatting mask that locks the caret past fixed prefixes.

- Available in: `@sisyphos-ui/react`, `@sisyphos-ui/vue`, `@sisyphos-ui/angular`
- Docs: https://sisyphosui.com/docs/components/input
- WAI-ARIA pattern: [Text input](https://www.w3.org/WAI/ARIA/apg/patterns/)

## Installation

Pick the framework binding that matches your stack:

```bash
pnpm add @sisyphos-ui/react   # React 18+
pnpm add @sisyphos-ui/vue     # Vue 3+
pnpm add @sisyphos-ui/angular # Angular 17+
```

## Import

```tsx
import "@sisyphos-ui/react/styles.css";
import { Input } from "@sisyphos-ui/react";
```

## Framework usage

### React 18+

```tsx
import { Input } from "@sisyphos-ui/react";

export const Email = () => (
  <Input
    label="Email"
    type="email"
    placeholder="name@example.com"
    error
    errorMessage="Please enter a valid email"
  />
);
```

### Vue 3+

```vue
<script setup lang="ts">
import { Input } from "@sisyphos-ui/vue";
</script>

<template>
  <Input
    label="Email"
    type="email"
    placeholder="name@example.com"
    :error="true"
    errorMessage="Please enter a valid email"
  />
</template>
```

### Angular 17+

```ts
import { Component } from "@angular/core";
import { Input } from "@sisyphos-ui/angular";

@Component({
  selector: "app-email",
  standalone: true,
  imports: [Input],
  template: `
    <sui-input
      label="Email"
      type="email"
      placeholder="name@example.com"
      [error]="true"
      errorMessage="Please enter a valid email"
    />
  `,
})
export class EmailComponent {}
```

## Examples

### Default

```tsx
import { useState } from "react";
import { Input } from "@sisyphos-ui/react";

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

  return (
    <div className="w-full max-w-sm">
      <Input
        label="Email"
        placeholder="you@company.com"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
    </div>
  );
}
```

### Error state

Set `error` + `errorMessage` to surface validation feedback.

```tsx
import { Input } from "@sisyphos-ui/react";

export function Example() {
  return (
    <div className="w-full max-w-sm">
      <Input
        label="Password"
        type="password"
        defaultValue="secret"
        error
        errorMessage="Password must be at least 12 characters."
      />
    </div>
  );
}
```

### Variants

```tsx
import { Input } from "@sisyphos-ui/react";

export function Example() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <Input variant="standard"  label="Standard"  placeholder="standard" />
      <Input variant="outlined"  label="Outlined"  placeholder="outlined" />
      <Input variant="underline" label="Underline" placeholder="underline" />
    </div>
  );
}
```

### With start icon

`startIcon` renders inside the input for search, currency, or context icons.

```tsx
import { Input } from "@sisyphos-ui/react";

const SearchIcon = () => (
  <svg viewBox="0 0 24 24" width={16} height={16} aria-hidden="true">
    <path fill="currentColor" d="M10 18a8 8 0 115.29-14A8 8 0 0110 18zm11 3l-5.2-5.2a10 10 0 10-1.4 1.4L19.6 22.4z" />
  </svg>
);

export function Example() {
  return (
    <Input
      startIcon={<SearchIcon />}
      placeholder="Search teams, projects, docs…"
      label="Search"
    />
  );
}
```

### Character count

Set `maxLength` + `showCharacterCount` to surface a live counter.

```tsx
import { useState } from "react";
import { Input } from "@sisyphos-ui/react";

export function Example() {
  const [v, setV] = useState("");

  return (
    <Input
      label="Display name"
      placeholder="How should we call you?"
      maxLength={30}
      showCharacterCount
      value={v}
      onChange={(e) => setV(e.target.value)}
    />
  );
}
```

### Label with help

Compose a label with an adjacent `Tooltip` + info glyph when the field needs inline guidance.

```tsx
import { Input, Tooltip } from "@sisyphos-ui/react";

export function Example() {
  return (
    <div className="flex flex-col gap-2">
      <div className="flex items-center gap-1">
        <label>Workspace subdomain</label>
        <Tooltip content="We'll create https://{subdomain}.sisyphos.app — only lowercase letters, digits, and hyphens.">
          <span aria-label="More info">?</span>
        </Tooltip>
      </div>
      <Input placeholder="acme" />
    </div>
  );
}
```

### Copyable value

Pair a read-only `Input` with an adjacent `Button` that writes the value to the clipboard via `navigator.clipboard.writeText`.

```tsx
import { useState } from "react";
import { Button, Input } from "@sisyphos-ui/react";

export function Example() {
  const link = "https://sisyphos.ui/invite/7c28a9f";
  const [copied, setCopied] = useState(false);

  return (
    <div className="flex items-center gap-2">
      <Input readOnly defaultValue={link} fullWidth />
      <Button
        variant="outlined"
        size="sm"
        onClick={async () => {
          await navigator.clipboard.writeText(link);
          setCopied(true);
          setTimeout(() => setCopied(false), 1500);
        }}
      >
        {copied ? "Copied" : "Copy"}
      </Button>
    </div>
  );
}
```

### Sizes

`size` scales the control for dense toolbars or hero forms.

```tsx
import { Input } from "@sisyphos-ui/react";

export function Example() {
  return (
    <>
      <Input size="sm" label="Small" placeholder="size=sm" />
      <Input size="md" label="Medium" placeholder="size=md" />
      <Input size="lg" label="Large" placeholder="size=lg" />
    </>
  );
}
```

### Real world — search row

Input paired with a Button inside a Card surface.

```tsx
import { Button, Card, Input } from "@sisyphos-ui/react";
import { useState } from "react";

export function SearchRow() {
  const [q, setQ] = useState("");
  return (
    <Card>
      <Card.Body>
        <div className="flex items-end gap-2">
          <div className="flex-1">
            <Input
              label="Search projects"
              placeholder="Type to filter…"
              value={q}
              onChange={(e) => setQ(e.target.value)}
            />
          </div>
          <Button>Search</Button>
        </div>
      </Card.Body>
    </Card>
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| variant | `"standard" \| "outlined" \| "underline"` | `"standard"` | Visual treatment. |
| size | `"sm" \| "md" \| "lg"` | `"md"` | Field height + typography scale. |
| radius | `"sm" \| "md" \| "lg" \| "full"` | — | Border radius scale. |
| label | `string` | — | Field label rendered above the input. |
| labelTooltip | `ReactNode` | — | Help content rendered as a tooltip next to the label. |
| labelTooltipPosition | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Anchors the label tooltip popover. |
| error | `boolean` | `false` | Marks the field as invalid for styling and ARIA. |
| errorMessage | `string` | — | Message shown below the field when `error` is true. |
| startIcon | `ReactNode` | — | Icon rendered inside the field, before the value. |
| endIcon | `ReactNode` | — | Icon rendered inside the field, after the value. |
| copyable | `boolean` | `false` | Renders a copy-to-clipboard button on the trailing side. |
| onCopy | `(value: string) => void` | — | Called after a successful clipboard copy. |
| showCharacterCount | `boolean` | `false` | Show a `current / max` counter (requires `maxLength`). |
| fullWidth | `boolean` | `false` | Stretch the input to its container width. |
| mask | `string` | — | Mask pattern (`#` digit, `A` letter, `*` alphanumeric, anything else literal). Presets: `tel-tr`, `tel`, `card`, `date`. Caret is locked past the fixed prefix automatically. |
| onUnmaskedChange | `(unmaskedValue: string) => void` | — | Fires with the raw value when `mask` is set. |

## Keyboard interactions

- **Tab** — Moves focus to the input.
- **Enter** — Submits the surrounding form.
- **Ctrl + A** — Selects the entire value (preserved even when a mask prefix is set).

## Accessibility notes

- Label is wired via `htmlFor` / `id`; tooltip-mode label uses `role="tooltip"` for the hint popover.
- `aria-invalid` mirrors `error`; the error message is announced with `role="alert"`.
- Character count and error message are linked through `aria-describedby`.
- Password and copy buttons carry descriptive `aria-label`s (and `aria-live="polite"` on the copy button).

<!-- exports: { "Input": "@sisyphos-ui/react" } -->