# Input

> An input element to enter text.

## Usage

Use the `v-model` directive to control the value of the Input.

```vue
<template>
  <PInput model-value="" />
</template>
```

### Type

Use the `type` prop to change the input type. Defaults to `text`.

Some types have been implemented in their own components such as [Checkbox](/docs/pohon/components/checkbox), [Radio](/docs/pohon/components/radio-group), [InputNumber](/docs/pohon/components/input-number) etc. and others have been styled like `file` for example.

```vue
<template>
  <PInput type="file" />
</template>
```

<callout icon="i-simple-icons:mdnwebdocs" target="_blank" to="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types">

You can check all the available types on the MDN Web Docs.

</callout>

### Placeholder

Use the `placeholder` prop to set a placeholder text.

```vue
<template>
  <PInput placeholder="Search..." />
</template>
```

### Color

Use the `color` prop to change the ring color when the Input is focused.

```vue
<template>
  <PInput color="neutral" highlight placeholder="Search..." />
</template>
```

<note>

The `highlight` prop is used here to show the focus state. It's used internally when a validation error occurs.

</note>

### Variant

Use the `variant` prop to change the variant of the Input.

```vue
<template>
  <PInput color="neutral" variant="subtle" :highlight="false" placeholder="Search..." />
</template>
```

### Size

Use the `size` prop to change the size of the Input.

```vue
<template>
  <PInput size="xl" placeholder="Search..." />
</template>
```

### Icon

Use the `icon` prop to show an [Icon](/docs/pohon/components/icon) inside the Input.

```vue
<template>
  <PInput icon="i-lucide:search" size="md" variant="outline" placeholder="Search..." />
</template>
```

Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.

```vue
<template>
  <PInput trailing-icon="i-lucide:at-sign" placeholder="Enter your email" size="md" />
</template>
```

### Avatar

Use the `avatar` prop to show an [Avatar](/docs/pohon/components/avatar) inside the Input.

```vue
<template>
  <PInput size="md" variant="outline" placeholder="Search..." />
</template>
```

### Loading

Use the `loading` prop to show a loading icon on the Input.

```vue
<template>
  <PInput loading :trailing="false" placeholder="Search..." />
</template>
```

### Loading Icon

Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide:loader-circle`.

```vue
<template>
  <PInput loading loading-icon="i-lucide:loader" placeholder="Search..." />
</template>
```

<docs-framework-only>
<template v-slot:nuxt="">
<tip to="/docs/pohon/getting-started/integrations/icons/nuxt#theme">

You can customize this icon globally in your `app.config.ts` under `pohon.icons.loading` key.

</tip>
</template>

<template v-slot:vue="">
<tip to="/docs/pohon/getting-started/integrations/icons/vue#theme">

You can customize this icon globally in your `vite.config.ts` under `pohon.icons.loading` key.

</tip>
</template>
</docs-framework-only>

### Disabled

Use the `disabled` prop to disable the Input.

```vue
<template>
  <PInput disabled placeholder="Search..." />
</template>
```

## Examples

### With clear button

You can put a [Button](/docs/pohon/components/button) inside the `#trailing` slot to clear the Input.

```vue [InputClearButtonExample.vue]
<script setup lang="ts">
import { ref } from 'vue';

const value = ref('Click to clear');
</script>

<template>
  <PInput
    v-model="value"
    placeholder="Type something..."
    :pohon="{ trailing: 'pe-1' }"
  >
    <template
      v-if="value?.length"
      #trailing
    >
      <PButton
        color="neutral"
        variant="link"
        size="sm"
        icon="i-lucide:circle-x"
        aria-label="Clear input"
        @click="value = ''"
      />
    </template>
  </PInput>
</template>
```

### With copy button

You can put a [Button](/docs/pohon/components/button) inside the `#trailing` slot to copy the value to the clipboard.

```vue [InputCopyButtonExample.vue]
<script setup lang="ts">
import { useClipboard } from '@vueuse/core';
import { ref } from 'vue';

const value = ref('npx nuxt module add pohon-ui');

const { copy, copied } = useClipboard();
</script>

<template>
  <PInput
    v-model="value"
    :pohon="{ trailing: 'pr-0.5' }"
  >
    <template
      v-if="value?.length"
      #trailing
    >
      <PTooltip
        text="Copy to clipboard"
        :content="{ side: 'right' }"
      >
        <PButton
          :color="copied ? 'success' : 'neutral'"
          variant="link"
          size="sm"
          :icon="copied ? 'i-lucide:copy-check' : 'i-lucide:copy'"
          aria-label="Copy to clipboard"
          @click="copy(value)"
        />
      </PTooltip>
    </template>
  </PInput>
</template>
```

### With password toggle

You can put a [Button](/docs/pohon/components/button) inside the `#trailing` slot to toggle the password visibility.

```vue [InputPasswordToggleExample.vue]
<script setup lang="ts">
import { ref } from 'vue';

const show = ref(false);
const password = ref('');
</script>

<template>
  <PInput
    v-model="password"
    placeholder="Password"
    :type="show ? 'text' : 'password'"
    :pohon="{ trailing: 'pe-1' }"
  >
    <template #trailing>
      <PButton
        color="neutral"
        variant="link"
        size="sm"
        :icon="show ? 'i-lucide:eye-off' : 'i-lucide:eye'"
        :aria-label="show ? 'Hide password' : 'Show password'"
        :aria-pressed="show"
        aria-controls="password"
        @click="show = !show"
      />
    </template>
  </PInput>
</template>

<style>
/* Hide the password reveal button in Edge */
::-ms-reveal {
  display: none;
}
</style>
```

### With password strength indicator

You can use the [Progress](/docs/pohon/components/progress) component to display the password strength indicator.

```vue [InputPasswordStrengthIndicatorExample.vue]
<script setup lang="ts">
import { computed, ref } from 'vue';

const show = ref(false);
const password = ref('');

function checkStrength(str: string) {
  const requirements = [
    { regex: /.{8,}/, text: 'At least 8 characters' },
    { regex: /\d/, text: 'At least 1 number' },
    { regex: /[a-z]/, text: 'At least 1 lowercase letter' },
    { regex: /[A-Z]/, text: 'At least 1 uppercase letter' },
  ];

  return requirements.map((req) => ({ met: req.regex.test(str), text: req.text }));
}

const strength = computed(() => checkStrength(password.value));
const score = computed(() => strength.value.filter((req) => req.met).length);

const color = computed(() => {
  if (score.value === 0) {
    return 'neutral';
  }
  if (score.value <= 1) {
    return 'error';
  }
  if (score.value <= 2) {
    return 'warning';
  }
  if (score.value === 3) {
    return 'warning';
  }
  return 'success';
});

const text = computed(() => {
  if (score.value === 0) {
    return 'Enter a password';
  }
  if (score.value <= 2) {
    return 'Weak password';
  }
  if (score.value === 3) {
    return 'Medium password';
  }
  return 'Strong password';
});
</script>

<template>
  <div class="space-y-2">
    <PFormField label="Password">
      <PInput
        v-model="password"
        placeholder="Password"
        :color="color"
        :type="show ? 'text' : 'password'"
        :aria-invalid="score < 4"
        aria-describedby="password-strength"
        :pohon="{ trailing: 'pe-1' }"
        class="w-full"
      >
        <template #trailing>
          <PButton
            color="neutral"
            variant="link"
            size="sm"
            :icon="show ? 'i-lucide:eye-off' : 'i-lucide:eye'"
            :aria-label="show ? 'Hide password' : 'Show password'"
            :aria-pressed="show"
            aria-controls="password"
            @click="show = !show"
          />
        </template>
      </PInput>
    </PFormField>

    <PProgress
      :color="color"
      :indicator="text"
      :model-value="score"
      :max="4"
      size="sm"
    />

    <p
      id="password-strength"
      class="text-sm font-medium"
    >
      {{ text }}. Must contain:
    </p>

    <ul
      class="space-y-1"
      aria-label="Password requirements"
    >
      <li
        v-for="(req, index) in strength"
        :key="index"
        class="flex gap-0.5 items-center"
        :class="req.met ? 'text-success' : 'color-text-muted'"
      >
        <PIcon
          :name="req.met ? 'i-lucide:circle-check' : 'i-lucide:circle-x'"
          class="shrink-0 size-4"
        />

        <span class="text-xs font-light">
          {{ req.text }}
          <span class="sr-only">
            {{ req.met ? ' - Requirement met' : ' - Requirement not met' }}
          </span>
        </span>
      </li>
    </ul>
  </div>
</template>
```

### With character limit

You can use the `#trailing` slot to add a character limit to the Input.

```vue [InputCharacterLimitExample.vue]
<script setup lang="ts">
import { ref } from 'vue';

const value = ref('');
const maxLength = 15;
</script>

<template>
  <PInput
    v-model="value"
    :maxlength="maxLength"
    aria-describedby="character-count"
    :pohon="{ trailing: 'pointer-events-none' }"
  >
    <template #trailing>
      <div
        id="character-count"
        class="text-xs color-text-muted tabular-nums"
        aria-live="polite"
        role="status"
      >
        {{ value?.length }}/{{ maxLength }}
      </div>
    </template>
  </PInput>
</template>
```

### With keyboard shortcut

You can use the [Kbd](/docs/pohon/components/kbd) component inside the `#trailing` slot to add a keyboard shortcut to the Input.

```vue [InputKbdExample.vue]
<script setup lang="ts">
import { defineShortcuts } from '#imports';
import { useTemplateRef } from 'vue';

const input = useTemplateRef('input');

defineShortcuts({
  config:
  {
    '/': () => {
      input.value?.inputRef?.focus();
    },
  },
});
</script>

<template>
  <PInput
    ref="input"
    icon="i-lucide:search"
    placeholder="Search..."
  >
    <template #trailing>
      <PKbd value="/" />
    </template>
  </PInput>
</template>
```

<note to="/docs/pohon/composables/define-shortcuts">

This example uses the `defineShortcuts` composable to focus the Input when the <kbd value="/">



</kbd>

 key is pressed.

</note>

### With mask

There's no built-in support for masks, but you can use libraries like [maska](https://github.com/beholdr/maska) to mask the Input.

```vue [InputMaskExample.vue]
<script setup lang="ts">
import { vMaska } from 'maska/vue';
</script>

<template>
  <div class="flex flex-col gap-2">
    <PInput
      v-maska="'#### #### #### ####'"
      placeholder="4242 4242 4242 4242"
      icon="i-lucide:credit-card"
    />

    <div class="flex gap-2 items-center">
      <PInput
        v-maska="'##/##'"
        placeholder="MM/YY"
        icon="i-lucide:calendar"
      />
      <PInput
        v-maska="'###'"
        placeholder="CVC"
      />
    </div>
  </div>
</template>
```

### Within a FormField

You can use the Input within a [FormField](/docs/pohon/components/form-field) component to display a label, help text, required indicator, etc.

```vue [InputFormFieldExample.vue]
<script setup lang="ts">
import { ref } from 'vue';

const email = ref('');
</script>

<template>
  <PFormField
    label="Email"
    help="We won't share your email."
    required
  >
    <PInput
      v-model="email"
      placeholder="Enter your email"
      icon="i-lucide:at-sign"
    />
  </PFormField>
</template>
```

<tip to="/docs/pohon/components/form">

It also provides validation and error handling when used within a **Form** component.

</tip>

### Within a FieldGroup

You can use the Input within a [FieldGroup](/docs/pohon/components/field-group) component to group multiple elements together.

```vue [InputFieldGroupExample.vue]
<script setup lang="ts">
import { ref } from 'vue';

const value = ref('');
const domains = ['.com', '.dev', '.org'];
const domain = ref(domains[0]);
</script>

<template>
  <PFieldGroup>
    <PInput
      v-model="value"
      placeholder="nuxt"
      :pohon="{
        base: 'pl-14.5',
        leading: 'pointer-events-none',
      }"
    >
      <template #leading>
        <p class="text-sm color-text-muted">
          https://
        </p>
      </template>
    </PInput>

    <PSelectMenu
      v-model="domain"
      :items="domains"
    />
  </PFieldGroup>
</template>
```

## API

### Props

```ts
/**
 * Props for the Input component
 */
interface InputProps {
  /**
   * The element or component this component should render as.
   */
  as?: any;
  id?: string;
  name?: string;
  /**
   * @default "\"text\""
   */
  type?: InputTypeHTMLAttribute;
  /**
   * The placeholder text when the input is empty.
   */
  placeholder?: string;
  color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral";
  variant?: "outline" | "soft" | "subtle" | "ghost" | "none";
  size?: "md" | "xs" | "sm" | "lg" | "xl";
  /**
   * @default "\"off\""
   */
  autocomplete?: (string & {}) | "on" | "off";
  /**
   * @default "0"
   */
  autofocusDelay?: number;
  modelValue?: _Number<_Optional<_Nullable<T, Mod>, Mod>, Mod>;
  defaultValue?: _Number<_Optional<_Nullable<T, Mod>, Mod>, Mod>;
  modelModifiers?: Mod;
  pohon?: { root?: ClassValue; base?: ClassValue; leading?: ClassValue; leadingIcon?: ClassValue; leadingAvatar?: ClassValue; leadingAvatarSize?: ClassValue; trailing?: ClassValue; trailingIcon?: ClassValue; };
  /**
   * Display an icon based on the `leading` and `trailing` props.
   */
  icon?: string | object;
  /**
   * Display an avatar on the left side.
   */
  avatar?: PAvatarProps;
  /**
   * Display an icon on the left side.
   */
  leadingIcon?: string | object;
  /**
   * Display an icon on the right side.
   */
  trailingIcon?: string | object;
  /**
   * The icon when the `loading` prop is `true`.
   */
  loadingIcon?: string | object;
  enterKeyHint?: "search" | "enter" | "done" | "go" | "next" | "previous" | "send";
  form?: string;
  formaction?: string;
  formenctype?: string;
  formmethod?: string;
  formnovalidate?: Booleanish;
  formtarget?: string;
  list?: string;
  max?: Numberish;
  maxlength?: Numberish;
  min?: Numberish;
  minlength?: Numberish;
  pattern?: string;
  readonly?: Booleanish;
  step?: Numberish;
  required?: boolean;
  autofocus?: boolean;
  disabled?: boolean;
  /**
   * Highlight the ring color like a focus state.
   */
  highlight?: boolean;
  /**
   * When `true`, the icon will be displayed on the left side.
   */
  leading?: boolean;
  /**
   * When `true`, the icon will be displayed on the right side.
   */
  trailing?: boolean;
  /**
   * When `true`, the loading icon will be displayed.
   */
  loading?: boolean;
}
```

<callout icon="i-simple-icons:mdnwebdocs" target="_blank" to="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attributes">

This component also supports all native `<input>` HTML attributes.

</callout>

### Slots

```ts
/**
 * Slots for the Input component
 */
interface InputSlots {
  leading(): any;
  default(): any;
  trailing(): any;
}
```

### Emits

```ts
/**
 * Emitted events for the Input component
 */
interface InputEmits {
  update:modelValue: (payload: [value: _Number<_Optional<_Nullable<T, Mod>, Mod>, Mod>]) => void;
  blur: (payload: [event: FocusEvent]) => void;
  change: (payload: [event: Event]) => void;
}
```

### Expose

When accessing the component via a template ref, you can use the following:

<table>
<thead>
  <tr>
    <th>
      Name
    </th>
    
    <th>
      Type
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        inputRef
      </code>
    </td>
    
    <td>
      <code>
        Ref<HTMLInputElement | null>
      </code>
    </td>
  </tr>
</tbody>
</table>

## Theme

```ts [app.config.ts]
export default defineAppConfig({
  pohon: {
    input: {
      slots: {
        root: 'relative inline-flex items-center',
        base: 'w-full rounded-md border-0 appearance-none placeholder:color-text-dimmed focus:outline-none disabled:(cursor-not-allowed opacity-75) transition-colors-280',
        leading: 'absolute inset-y-0 start-0 flex items-center',
        leadingIcon: 'shrink-0 color-text-dimmed',
        leadingAvatar: 'shrink-0',
        trailing: 'absolute inset-y-0 end-0 flex items-center',
        trailingIcon: 'shrink-0 color-text-dimmed'
      },
      variants: {
        fieldGroup: {
          horizontal: {
            root: 'group has-focus-visible:z-1',
            base: 'group-not-[*:only-child]:group-first:rounded-e-none group-not-[*:only-child]:group-last:rounded-s-none group-not-last:group-not-first:rounded-none'
          },
          vertical: {
            root: 'group has-focus-visible:z-1',
            base: 'group-not-[*:only-child]:group-first:rounded-b-none group-not-[*:only-child]:group-last:rounded-t-none group-not-last:group-not-first:rounded-none'
          }
        },
        size: {
          xs: {
            base: 'px-2 py-1 text-xs gap-1',
            leading: 'ps-2',
            trailing: 'pe-2',
            leadingIcon: 'size-4',
            leadingAvatarSize: '3xs',
            trailingIcon: 'size-4'
          },
          sm: {
            base: 'px-2.5 py-1.5 text-xs gap-1.5',
            leading: 'ps-2.5',
            trailing: 'pe-2.5',
            leadingIcon: 'size-4',
            leadingAvatarSize: '3xs',
            trailingIcon: 'size-4'
          },
          md: {
            base: 'px-2.5 py-1.5 text-sm gap-1.5',
            leading: 'ps-2.5',
            trailing: 'pe-2.5',
            leadingIcon: 'size-5',
            leadingAvatarSize: '2xs',
            trailingIcon: 'size-5'
          },
          lg: {
            base: 'px-3 py-2 text-sm gap-2',
            leading: 'ps-3',
            trailing: 'pe-3',
            leadingIcon: 'size-5',
            leadingAvatarSize: '2xs',
            trailingIcon: 'size-5'
          },
          xl: {
            base: 'px-3 py-2 text-base gap-2',
            leading: 'ps-3',
            trailing: 'pe-3',
            leadingIcon: 'size-6',
            leadingAvatarSize: 'xs',
            trailingIcon: 'size-6'
          }
        },
        variant: {
          outline: 'color-text-highlighted bg-background ring ring-inset ring-ring-accented',
          soft: 'color-text-highlighted bg-background-elevated/50 hover:bg-background-elevated focus:bg-background-elevated disabled:bg-background-elevated/50',
          subtle: 'color-text-highlighted bg-background-elevated ring ring-inset ring-ring-accented',
          ghost: 'color-text-highlighted bg-transparent hover:bg-background-elevated focus:bg-background-elevated disabled:bg-transparent dark:disabled:bg-transparent',
          none: 'color-text-highlighted bg-transparent'
        },
        type: {
          file: 'file:me-1.5 file:font-medium file:color-text-muted file:outline-none'
        }
      },
      compoundVariants: [
        {
          color: 'primary',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'focus-visible:(ring-2 ring-inset) focus-visible:ring-primary'
        },
        {
          color: 'secondary',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'focus-visible:(ring-2 ring-inset) focus-visible:ring-secondary'
        },
        {
          color: 'success',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'focus-visible:(ring-2 ring-inset) focus-visible:ring-success'
        },
        {
          color: 'info',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'focus-visible:(ring-2 ring-inset) focus-visible:ring-info'
        },
        {
          color: 'warning',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'focus-visible:(ring-2 ring-inset) focus-visible:ring-warning'
        },
        {
          color: 'error',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'focus-visible:(ring-2 ring-inset) focus-visible:ring-error'
        },
        {
          color: 'primary',
          highlight: true,
          class: 'ring ring-inset akar:ring-primary'
        },
        {
          color: 'secondary',
          highlight: true,
          class: 'ring ring-inset akar:ring-secondary'
        },
        {
          color: 'success',
          highlight: true,
          class: 'ring ring-inset akar:ring-success'
        },
        {
          color: 'info',
          highlight: true,
          class: 'ring ring-inset akar:ring-info'
        },
        {
          color: 'warning',
          highlight: true,
          class: 'ring ring-inset akar:ring-warning'
        },
        {
          color: 'error',
          highlight: true,
          class: 'ring ring-inset akar:ring-error'
        },
        {
          color: 'neutral',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'focus-visible:(ring-2 ring-inset akar:ring-ring-inverted)'
        },
        {
          color: 'neutral',
          highlight: true,
          class: 'ring ring-inset akar:ring-ring-inverted'
        },
        {
          leading: true,
          size: 'xs',
          class: 'ps-7'
        },
        {
          leading: true,
          size: 'sm',
          class: 'ps-8'
        },
        {
          leading: true,
          size: 'md',
          class: 'ps-9'
        },
        {
          leading: true,
          size: 'lg',
          class: 'ps-10'
        },
        {
          leading: true,
          size: 'xl',
          class: 'ps-11'
        },
        {
          trailing: true,
          size: 'xs',
          class: 'pe-7'
        },
        {
          trailing: true,
          size: 'sm',
          class: 'pe-8'
        },
        {
          trailing: true,
          size: 'md',
          class: 'pe-9'
        },
        {
          trailing: true,
          size: 'lg',
          class: 'pe-10'
        },
        {
          trailing: true,
          size: 'xl',
          class: 'pe-11'
        },
        {
          loading: true,
          leading: true,
          class: {
            leadingIcon: 'animate-spin'
          }
        },
        {
          loading: true,
          leading: false,
          trailing: true,
          class: {
            trailingIcon: 'animate-spin'
          }
        }
      ]
    }
  }
})
```

## Changelog

<docs-component-changelog>



</docs-component-changelog>
