Input

GitHub
一个用于输入文本的输入元素。

用法

使用 v-model 指令来控制输入框的值。

<script setup lang="ts">
const value = ref('')
</script>

<template>
  <UInput v-model="value" />
</template>

类型

使用 type 属性来改变输入框的类型。默认为 text

某些类型已在各自的组件中实现,例如 CheckboxRadioInputNumber 等,而其他类型则经过样式化,例如 file

<template>
  <UInput type="file" />
</template>
您可以在 MDN Web Docs 上查看所有可用类型。

占位符

使用 placeholder 属性设置占位文本。

<template>
  <UInput placeholder="Search..." />
</template>

颜色

使用 color 属性来改变输入框聚焦时的环形颜色。

<template>
  <UInput color="neutral" highlight placeholder="Search..." />
</template>
highlight 属性在此用于显示焦点状态。当发生验证错误时,它会在内部使用。

变体

使用 variant 属性来改变输入框的变体。

<template>
  <UInput color="neutral" variant="subtle" placeholder="Search..." />
</template>

尺寸

使用 size 属性来改变输入框的大小。

<template>
  <UInput size="xl" placeholder="Search..." />
</template>

Icon

使用 icon 属性来在输入框内显示一个 Icon

<template>
  <UInput icon="i-lucide-search" size="md" variant="outline" placeholder="Search..." />
</template>

使用 leadingtrailing 属性设置图标位置,或者使用 leading-icontrailing-icon 属性为每个位置设置不同的图标。

<template>
  <UInput trailing-icon="i-lucide-at-sign" placeholder="Enter your email" size="md" />
</template>

Avatar

使用 avatar 属性来在输入框内显示一个 Avatar

<template>
  <UInput
    :avatar="{
      src: 'https://github.com/nuxt.png'
    }"
    size="md"
    variant="outline"
    placeholder="Search..."
  />
</template>

加载中

使用 loading 属性来在输入框上显示加载图标。

<template>
  <UInput loading placeholder="Search..." />
</template>

加载图标

使用 loading-icon prop 来自定义加载图标。默认为 i-lucide-loader-circle

<template>
  <UInput loading loading-icon="i-lucide-loader" placeholder="Search..." />
</template>
你可以在 app.config.ts 中的 ui.icons.loading 键下全局自定义此图标。
你可以在 vite.config.ts 中的 ui.icons.loading 键下全局自定义此图标。

禁用

使用 disabled 属性来禁用输入框。

<template>
  <UInput disabled placeholder="Search..." />
</template>

示例

带清除按钮

您可以在 #trailing 插槽中放置一个 Button 来清除输入框。

<script setup lang="ts">
const value = ref('Click to clear')
</script>

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

带复制按钮

您可以在 #trailing 插槽中放置一个 Button 来将值复制到剪贴板。

<script setup lang="ts">
import { useClipboard } from '@vueuse/core'

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

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

<template>
  <UInput
    v-model="value"
    :ui="{ trailing: 'pr-0.5' }"
  >
    <template v-if="value?.length" #trailing>
      <UTooltip text="Copy to clipboard" :content="{ side: 'right' }">
        <UButton
          :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)"
        />
      </UTooltip>
    </template>
  </UInput>
</template>

带密码切换

您可以在 #trailing 插槽中放置一个 Button 来切换密码可见性。

<script setup lang="ts">
const show = ref(false)
const password = ref('')
</script>

<template>
  <UInput
    v-model="password"
    placeholder="Password"
    :type="show ? 'text' : 'password'"
    :ui="{ trailing: 'pe-1' }"
  >
    <template #trailing>
      <UButton
        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>
  </UInput>
</template>

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

带密码强度指示器

您可以使用 Progress 组件来显示密码强度指示器。

输入密码。必须包含

  • 至少 8 个字符 - 未满足要求
  • 至少 1 个数字 - 未满足要求
  • 至少 1 个小写字母 - 未满足要求
  • 至少 1 个大写字母 - 未满足要求
<script setup lang="ts">
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">
    <UFormField label="Password">
      <UInput
        v-model="password"
        placeholder="Password"
        :color="color"
        :type="show ? 'text' : 'password'"
        :aria-invalid="score < 4"
        aria-describedby="password-strength"
        :ui="{ trailing: 'pe-1' }"
        class="w-full"
      >
        <template #trailing>
          <UButton
            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>
      </UInput>
    </UFormField>

    <UProgress
      :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 items-center gap-0.5"
        :class="req.met ? 'text-success' : 'text-muted'"
      >
        <UIcon :name="req.met ? 'i-lucide-circle-check' : 'i-lucide-circle-x'" class="size-4 shrink-0" />

        <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>

带字符限制

您可以使用 #trailing 插槽来为输入框添加字符限制。

0/15
<script setup lang="ts">
const value = ref('')
const maxLength = 15
</script>

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

带键盘快捷键

您可以在 #trailing 插槽中放置 Kbd 组件,为输入框添加键盘快捷键。

/
<script setup lang="ts">
const input = useTemplateRef('input')

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

<template>
  <UInput
    ref="input"
    icon="i-lucide-search"
    placeholder="Search..."
  >
    <template #trailing>
      <UKbd value="/" />
    </template>
  </UInput>
</template>
此示例使用 defineShortcuts 组合函数,当按下 / 键时,聚焦输入框。

带遮罩

没有内置的掩码支持,但您可以使用像maska这样的库来掩盖输入框。

<script setup lang="ts">
import { vMaska } from 'maska/vue'
</script>

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

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

带浮动标签

您可以使用 #default 插槽为输入框添加浮动标签。

<script setup lang="ts">
const value = ref('')
</script>

<template>
  <UInput v-model="value" placeholder="" :ui="{ base: 'peer' }">
    <label class="pointer-events-none absolute left-0 -top-2.5 text-highlighted text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-highlighted peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-dimmed peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal">
      <span class="inline-flex bg-default px-1">Email address</span>
    </label>
  </UInput>
</template>

在 FormField 中

您可以在 FormField 组件中使用输入框,以显示标签、帮助文本、必填指示器等。

我们不会分享您的电子邮件。
<script setup lang="ts">
const email = ref('')
</script>

<template>
  <UFormField label="Email" help="We won't share your email." required>
    <UInput v-model="email" placeholder="Enter your email" icon="i-lucide-at-sign" />
  </UFormField>
</template>
当在 Form 组件中使用时,它还提供验证和错误处理。

在 FieldGroup 中

您可以在 FieldGroup 组件中使用输入框,将多个元素组合在一起。

https://

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

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

    <USelectMenu v-model="domain" :items="domains" />
  </UFieldGroup>
</template>

API

属性

属性默认值类型
as

'div'

any

此组件应渲染为的元素或组件。

id

string

name

string

type

'text'

"number" | "color" | "button" | "checkbox" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "password" | "radio" | "range" | "reset" | "search" | "submit" | "tel" | "text" | "time" | "url" | "week" | string & {}

placeholder

string

输入为空时的占位文本。

color

'主要'

"primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"

variant

'outline'

"outline" | "soft" | "subtle" | "ghost" | "none"

尺寸

'md'

"xs" | "sm" | "md" | "lg" | "xl"

required

boolean

自动完成

'off'

string

autofocus

boolean

autofocusDelay

0

number

disabled

boolean

高亮

boolean

高亮环形颜色,如同焦点状态。

modelValue

null | string | number | bigint | false | true

defaultValue

null | string | number | bigint | false | true

modelModifiers

模型修饰符

图标

字符串 | 对象

根据 leadingtrailing 属性显示图标。

avatar

AvatarProps

在左侧显示头像。

前置

boolean

当为 true 时,图标将显示在左侧。

leadingIcon

字符串 | 对象

在左侧显示图标。

尾部

boolean

当为 true 时,图标将显示在右侧。

trailingIcon

字符串 | 对象

在右侧显示图标。

loading

boolean

当为 true 时,将显示加载图标。

loadingIcon

appConfig.ui.icons.loading

字符串 | 对象

loading prop 为 true 时显示的图标。

ui

{ root?: ClassNameValue; base?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailing?: ClassNameValue; trailingIcon?: ClassNameValue; }

插槽

插槽类型
前置

{}

default

{}

尾部

{}

事件

事件类型
update:modelValue

[value: AcceptableValue]

blur

[event: FocusEvent]

change

[event: Event]

可访问属性

通过模板引用访问组件时,您可以使用以下内容:

名称类型
inputRefRef<HTMLInputElement | null>

主题

app.config.ts
export default defineAppConfig({
  ui: {
    input: {
      slots: {
        root: 'relative inline-flex items-center',
        base: [
          'w-full rounded-md border-0 appearance-none placeholder:text-dimmed focus:outline-none disabled:cursor-not-allowed disabled:opacity-75',
          'transition-colors'
        ],
        leading: 'absolute inset-y-0 start-0 flex items-center',
        leadingIcon: 'shrink-0 text-dimmed',
        leadingAvatar: 'shrink-0',
        leadingAvatarSize: '',
        trailing: 'absolute inset-y-0 end-0 flex items-center',
        trailingIcon: 'shrink-0 text-dimmed'
      },
      variants: {
        fieldGroup: {
          horizontal: {
            root: 'group has-focus-visible:z-[1]',
            base: 'group-not-only:group-first:rounded-e-none group-not-only: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:group-first:rounded-b-none group-not-only: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: 'text-highlighted bg-default ring ring-inset ring-accented',
          soft: 'text-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50',
          subtle: 'text-highlighted bg-elevated ring ring-inset ring-accented',
          ghost: 'text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent',
          none: 'text-highlighted bg-transparent'
        },
        color: {
          primary: '',
          secondary: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        leading: {
          true: ''
        },
        trailing: {
          true: ''
        },
        loading: {
          true: ''
        },
        highlight: {
          true: ''
        },
        type: {
          file: 'file:me-1.5 file:font-medium file:text-muted file:outline-none'
        }
      },
      compoundVariants: [
        {
          color: 'primary',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary'
        },
        {
          color: 'primary',
          highlight: true,
          class: 'ring ring-inset ring-primary'
        },
        {
          color: 'neutral',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-inverted'
        },
        {
          color: 'neutral',
          highlight: true,
          class: 'ring ring-inset 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'
          }
        }
      ],
      defaultVariants: {
        size: 'md',
        color: 'primary',
        variant: 'outline'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        input: {
          slots: {
            root: 'relative inline-flex items-center',
            base: [
              'w-full rounded-md border-0 appearance-none placeholder:text-dimmed focus:outline-none disabled:cursor-not-allowed disabled:opacity-75',
              'transition-colors'
            ],
            leading: 'absolute inset-y-0 start-0 flex items-center',
            leadingIcon: 'shrink-0 text-dimmed',
            leadingAvatar: 'shrink-0',
            leadingAvatarSize: '',
            trailing: 'absolute inset-y-0 end-0 flex items-center',
            trailingIcon: 'shrink-0 text-dimmed'
          },
          variants: {
            fieldGroup: {
              horizontal: {
                root: 'group has-focus-visible:z-[1]',
                base: 'group-not-only:group-first:rounded-e-none group-not-only: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:group-first:rounded-b-none group-not-only: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: 'text-highlighted bg-default ring ring-inset ring-accented',
              soft: 'text-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50',
              subtle: 'text-highlighted bg-elevated ring ring-inset ring-accented',
              ghost: 'text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent',
              none: 'text-highlighted bg-transparent'
            },
            color: {
              primary: '',
              secondary: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            leading: {
              true: ''
            },
            trailing: {
              true: ''
            },
            loading: {
              true: ''
            },
            highlight: {
              true: ''
            },
            type: {
              file: 'file:me-1.5 file:font-medium file:text-muted file:outline-none'
            }
          },
          compoundVariants: [
            {
              color: 'primary',
              variant: [
                'outline',
                'subtle'
              ],
              class: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary'
            },
            {
              color: 'primary',
              highlight: true,
              class: 'ring ring-inset ring-primary'
            },
            {
              color: 'neutral',
              variant: [
                'outline',
                'subtle'
              ],
              class: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-inverted'
            },
            {
              color: 'neutral',
              highlight: true,
              class: 'ring ring-inset 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'
              }
            }
          ],
          defaultVariants: {
            size: 'md',
            color: 'primary',
            variant: 'outline'
          }
        }
      }
    })
  ]
})
为便于阅读,compoundVariants 中的某些颜色已省略。请在 GitHub 上查看源代码。

更新日志

788d2— 修复:标准化类型接口命名 (#4990)

83b03— feat!: 重命名 nullify 修饰符为 nullable 并添加 optional (#4838)

a0963— feat!: 重命名 ButtonGroup (#4596)

5cb65— 特性:导入 @nuxt/ui-pro 组件

7133f— 修复:update:model-value 事件类型错误 (#4853)

6f2ce— 重构:统一 emits 声明的语法 (#4512)

fb9e7— feat: 添加 default-value 属性 (#4404)

3243f— fix: 定义模型修饰符类型 (#4195)

2e4c3— fix: 处理按钮组内部

3c8d6— fix: 处理泛型

e6e51— fix:class 应该优先于 ui 属性

39c86— fix:@nuxt/module-builder 升级后重构类型(#3855)

4d817— chore: 清理函数顺序