---
name: input
title: Input
url: https://ui.frontic.com/components/ui/input.md
tier: ui
install: npx @frontic/ui add input
files:
  - components/ui/input/Input.vue
  - components/ui/input/index.ts
dependencies:
  - "@vueuse/core"
  - class-variance-authority
  - reka-ui
---

# Input

Lets the user enter a single line of text, joining a Field.

## Install

```sh
npx @frontic/ui add input
```

Used by: [Input Group](https://ui.frontic.com/components/ui/input-group.md), [Sidebar](https://ui.frontic.com/components/ui/sidebar.md), [Account Access](https://ui.frontic.com/components/sections/account-access.md), [Footer Brand](https://ui.frontic.com/components/sections/footer-brand.md), [Footer Rich](https://ui.frontic.com/components/sections/footer-rich.md), [Shipping Address](https://ui.frontic.com/components/sections/shipping-address.md), [Sign In](https://ui.frontic.com/components/sections/sign-in.md), [Team Members](https://ui.frontic.com/components/sections/team-members.md), [Checkout Page](https://ui.frontic.com/components/templates/checkout-page.md), [Filter Toolbar](https://ui.frontic.com/components/templates/filter-toolbar.md)

npm packages: `@vueuse/core`, `class-variance-authority`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { Input } from '@/components/ui/input'
</script>

<template>
  <Input type="email" placeholder="you@example.com" autocomplete="email" aria-label="Email" class="max-w-sm" />
</template>
```

## API reference

### Input

Lets the user enter a single line of text. Renders a `<input>`.

Styling: `input`, which extends [`control`](https://ui.frontic.com/DESIGN.md#control) and [`control-line`](https://ui.frontic.com/DESIGN.md#control-line). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultValue` | `string \| number` |  | The starting value when the field is not bound with v-model. |
| `modelValue` | `string \| number` |  | The field's value, bound with v-model. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `size` | `'md' \| 'sm'` |  | The height of the input. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `payload: string \| number` | Fires with the new value when `modelValue` changes; `v-model` binds it. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-size` | `'md' \| 'sm'` | Carries the `size` prop. |

## Styling

| Part | Recipe |
| --- | --- |
| `default` | [`input`](https://ui.frontic.com/DESIGN.md#input) |
| `disabled` | [`disabled`](https://ui.frontic.com/DESIGN.md#disabled) |

States with a look of their own: focus, disabled and invalid.

## Source

`components/ui/input/Input.vue`

```vue
<script lang="ts">
/** Lets the user enter a single line of text. */
export default {}
</script>

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { useVModel } from '@vueuse/core'
import { cva, type VariantProps } from 'class-variance-authority'
import { FieldControl, injectFieldRootContext, Slot } from 'reka-ui'

const input = cva('outline-none transition-colors fui-input file:h-7 disabled:pointer-events-none disabled:cursor-not-allowed disabled:fui-disabled', {
  variants: {
    size: { md: 'fui-input-md', sm: 'fui-input-sm' },
  },
  defaultVariants: { size: 'md' },
})

const props = defineProps<{
  /** The starting value when the field is not bound with v-model. */
  defaultValue?: string | number
  /** The field's value, bound with v-model. */
  modelValue?: string | number
  class?: HTMLAttributes['class']
  /** The height of the input. */
  size?: VariantProps<typeof input>['size']
}>()

const emits = defineEmits<{
  (e: 'update:modelValue', payload: string | number): void
}>()

const modelValue = useVModel(props, 'modelValue', emits, {
  passive: true,
  defaultValue: props.defaultValue,
})

/** Inside a Field the input joins it through Reka's FieldControl; elsewhere it renders alone. Both roots keep the child's own data-slot over a wrapper's, so the template takes a wrapper's from $attrs. */
const field = injectFieldRootContext(null)
</script>

<template>
  <component :is="field ? FieldControl : Slot" v-bind="field ? { asChild: true, modelValue } : {}">
    <input v-model="modelValue" :data-slot="$attrs['data-slot'] ?? 'input'" :data-size="props.size ?? 'md'" :class="[input({ size: props.size }), props.class]" />
  </component>
</template>
```
