Input
Usage
v-model 지시문을 사용하여 Input 값을 제어합니다.
<script setup lang="ts">
const value = ref('')
</script>
<template>
<UInput v-model="value" />
</template><script setup lang="ts">
import { ref } from 'vue'
const value = ref('')
</script>
<template>
<UInput v-model="value" />
</template>Type 형식
type prop을 사용하여 입력 유형을 변경합니다. 기본값은 text입니다.
일부 타입은 Checkbox, , InputNumber 등과 같은 자체 컴포넌트에서 구현되었으며 다른 타입은 file와 같이 스타일이 지정되었습니다.
<template>
<UInput type="file" />
</template>자리 표시자
placeholder 소품을 사용하여 자리 표시자 텍스트를 설정합니다.
<template>
<UInput placeholder="Search..." />
</template>Color
color Prop을 사용하여 입력에 초점을 맞출 때 링 색상을 변경합니다.
<template>
<UInput color="neutral" highlight placeholder="Search..." />
</template>highlight prop은 초점 상태를 표시하기 위해 사용되며, 유효성 검사 오류가 발생할 때 내부적으로 사용됩니다.변형
variant prop 을 사용하여 Input 의 변형을 변경합니다.
<template>
<UInput color="neutral" variant="subtle" placeholder="Search..." />
</template>Size
size Prop을 사용하여 Input의 크기를 변경합니다.
<template>
<UInput size="xl" placeholder="Search..." />
</template>Icon 이미지
icon prop을 사용하여 입력 안에 Icon를 표시합니다.
<template>
<UInput icon="i-lucide-search" size="md" variant="outline" placeholder="Search..." />
</template>leading 및 trailing 소품을 사용하여 아이콘 위치를 설정하거나 leading-icon 및 trailing-icon 소품을 사용하여 각 위치에 대해 다른 아이콘을 설정합니다.
<template>
<UInput trailing-icon="i-lucide-at-sign" placeholder="Enter your email" size="md" />
</template>Avatar 이미지
avatar prop을 사용하여 입력 내부에 Avatar를 표시합니다.
<template>
<UInput
:avatar="{
src: 'https://github.com/nuxt.png',
loading: 'lazy'
}"
size="md"
variant="outline"
placeholder="Search..."
/>
</template>loading 파일
loading prop을 사용하여 입력에 로드 아이콘을 표시합니다.
<template>
<UInput loading placeholder="Search..." />
</template>Loading 아이콘
loading-icon 소품을 사용하여 로드 아이콘을 사용자 정의합니다. 기본값은 i-lucide-loader-circle입니다.
<template>
<UInput loading loading-icon="i-lucide-loader" placeholder="Search..." />
</template>비활성 화 됨
disabled prop을 사용하여 Input을 비활성화합니다.
<template>
<UInput disabled placeholder="Search..." />
</template>examples 예제
Clear 버튼 사용
Button를 #trailing 슬롯 안에 넣어 입력을 지울 수 있습니다.
<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>
<script setup lang="ts">
import { ref } from 'vue'
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>
With 복사 버튼
#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>
<script setup lang="ts">
import { ref } from 'vue'
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>
With 암호 토글
Button를 #trailing 슬롯 안에 넣어 암호 표시를 토글할 수 있습니다.
<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>
<script setup lang="ts">
import { ref } from 'vue'
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>
With 암호 강도 표시기
Progress 구성 요소를 사용하여 암호 강도 표시기를 표시할 수도 있습니다.
Enter a password. Must contain:
- At least 8 characters - Requirement not met
- At least 1 number - Requirement not met
- At least 1 lowercase letter - Requirement not met
- At least 1 uppercase letter - Requirement not met
<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>
<script setup lang="ts">
import { ref, computed } 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">
<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 슬롯을 사용하여 입력에 문자 한계를 추가할 수 있습니다.
<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>
<script setup lang="ts">
import { ref } from 'vue'
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>
<script setup lang="ts">
import { useTemplateRef } from 'vue'
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>
with 마스크
마스크에 대한 기본 제공 지원은 없지만 maska와 같은 라이브러리를 사용하여 Input을 마스크 할 수 있습니다.
<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="" variant="outline" :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>
<script setup lang="ts">
import { ref } from 'vue'
const value = ref('')
</script>
<template>
<UInput v-model="value" placeholder="" variant="outline" :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>
within a 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>
<script setup lang="ts">
import { ref } from 'vue'
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>
Within a FieldGroup 필드 그룹 내에서
FieldGroup 구성 요소 내에서 Input을 사용하여 여러 요소를 그룹화할 수 있습니다.
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>
<script setup lang="ts">
import { ref } from 'vue'
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>
As 전화 번호 입력
FieldGroup 구성 요소 내에서 입력을 SelectMenu와 함께 사용하여 국가 코드를 선택하여 전화 번호 입력을 만들 수 있습니다.
<script setup lang="ts">
import { vMaska } from 'maska/vue'
type PhoneCode = {
name: string
code: string
emoji: string
dialCode: string
mask: string
}
const phone = ref('')
const countryCode = ref('US')
const { data: phoneCodes, status, execute } = await useLazyFetch<PhoneCode[]>('/api/phone-codes.json', {
key: 'api-phone-codes',
immediate: false
})
const country = computed(() => phoneCodes.value?.find(c => c.code === countryCode.value))
const dialCode = computed(() => country.value?.dialCode || '+1')
const mask = computed(() => country.value?.mask || '(###) ###-####')
function onOpen() {
if (!phoneCodes.value?.length) {
execute()
}
}
watch(countryCode, () => {
phone.value = ''
})
</script>
<template>
<UFieldGroup>
<USelectMenu
v-model="countryCode"
:items="phoneCodes"
value-key="code"
:search-input="{
placeholder: 'Search country...',
icon: 'i-lucide-search',
loading: status === 'pending'
}"
:filter-fields="['name', 'code', 'dialCode']"
:content="{ align: 'start' }"
:ui="{
base: 'pe-8',
content: 'w-48',
placeholder: 'hidden',
trailingIcon: 'size-4'
}"
trailing-icon="i-lucide-chevrons-up-down"
@update:open="onOpen"
>
<span class="size-5 flex items-center text-lg">
{{ country?.emoji || '\u{1F1FA}\u{1F1F8}' }}
</span>
<template #item-leading="{ item }">
<span class="size-5 flex items-center text-lg">
{{ item.emoji }}
</span>
</template>
<template #item-label="{ item }">
{{ item.name }} ({{ item.dialCode }})
</template>
</USelectMenu>
<UInput
v-model="phone"
v-maska="mask"
:placeholder="mask.replaceAll('#', '_')"
:style="{ '--dial-code-length': `${dialCode.length + 1.5}ch` }"
:ui="{
base: 'ps-(--dial-code-length)',
leading: 'pointer-events-none text-base md:text-sm text-muted'
}"
>
<template #leading>
{{ dialCode }}
</template>
</UInput>
</UFieldGroup>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { vMaska } from 'maska/vue'
type PhoneCode = {
name: string
code: string
emoji: string
dialCode: string
mask: string
}
const phone = ref('')
const countryCode = ref('US')
const { data: phoneCodes, status, execute } = await useLazyFetch<PhoneCode[]>('/api/phone-codes.json', {
key: 'api-phone-codes',
immediate: false
})
const country = computed(() => phoneCodes.value?.find(c => c.code === countryCode.value))
const dialCode = computed(() => country.value?.dialCode || '+1')
const mask = computed(() => country.value?.mask || '(###) ###-####')
function onOpen() {
if (!phoneCodes.value?.length) {
execute()
}
}
watch(countryCode, () => {
phone.value = ''
})
</script>
<template>
<UFieldGroup>
<USelectMenu
v-model="countryCode"
:items="phoneCodes"
value-key="code"
:search-input="{
placeholder: 'Search country...',
icon: 'i-lucide-search',
loading: status === 'pending'
}"
:filter-fields="['name', 'code', 'dialCode']"
:content="{ align: 'start' }"
:ui="{
base: 'pe-8',
content: 'w-48',
placeholder: 'hidden',
trailingIcon: 'size-4'
}"
trailing-icon="i-lucide-chevrons-up-down"
@update:open="onOpen"
>
<span class="size-5 flex items-center text-lg">
{{ country?.emoji || '\u{1F1FA}\u{1F1F8}' }}
</span>
<template #item-leading="{ item }">
<span class="size-5 flex items-center text-lg">
{{ item.emoji }}
</span>
</template>
<template #item-label="{ item }">
{{ item.name }} ({{ item.dialCode }})
</template>
</USelectMenu>
<UInput
v-model="phone"
v-maska="mask"
:placeholder="mask.replaceAll('#', '_')"
:style="{ '--dial-code-length': `${dialCode.length + 1.5}ch` }"
:ui="{
base: 'ps-(--dial-code-length)',
leading: 'pointer-events-none text-base md:text-sm text-muted'
}"
>
<template #leading>
{{ dialCode }}
</template>
</UInput>
</UFieldGroup>
</template>
API
Props (### Props)
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
id | string | |
name | string | |
type | 'text' | "number" | "image" | "text" | "button" | "search" | "time" | "color" | "checkbox" | "date" | "datetime-local" | "email" | "file" | "hidden" | "month" | "password" | "radio" | "range" | "reset" | "submit" | "tel" | "url" | "week" | string |
placeholder | stringThe placeholder text when the input is empty. | |
color | 'primary' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" |
variant | 'outline' | "outline" | "soft" | "subtle" | "ghost" | "none" |
size | 'md' | "xs" | "sm" | "md" | "lg" | "xl" |
required | boolean | |
autocomplete | 'off' | string | "on" | "off" |
autofocus | boolean | |
autofocusDelay | 0 | number |
disabled | boolean | |
highlight | boolean Highlight the ring color like a focus state. | |
fixed | boolean Keep the mobile text size on all breakpoints. | |
modelValue | _Number<_Optional<_Nullable<T, Mod>, Mod>, Mod> | |
defaultValue | _Number<_Optional<_Nullable<T, Mod>, Mod>, Mod> | |
modelModifiers | Mod | |
icon | anyDisplay an icon based on the | |
avatar | AvatarPropsDisplay an avatar on the left side. | |
leading | boolean When | |
leadingIcon | anyDisplay an icon on the left side. | |
trailing | boolean When | |
trailingIcon | anyDisplay an icon on the right side. | |
loading | boolean When | |
loadingIcon | appConfig.ui.icons.loading | anyThe icon when the |
list | string | |
max | string | number | |
maxlength | string | number | |
min | string | number | |
minlength | string | number | |
pattern | string | |
readonly | false | true | "true" | "false" | |
step | string | number | |
ui | { root?: SlotClass; base?: SlotClass; leading?: SlotClass; leadingIcon?: SlotClass; leadingAvatar?: SlotClass; leadingAvatarSize?: SlotClass; trailing?: SlotClass; trailingIcon?: SlotClass; } |
슬롯
| Slot | Type |
|---|---|
leading | { ui: object; } |
default | { ui: object; } |
trailing | { ui: object; } |
Emits
| Event | Type |
|---|---|
update:modelValue | [value: _Number<_Optional<_Nullable<T, Mod>, Mod>, Mod>] |
blur | [event: FocusEvent] |
change | [event: Event] |
exose 소개
템플릿 참조를 통해 컴포넌트에 액세스하는 경우 다음을 사용할 수 있습니다.
| 이름 (Name) | 유형 (Type) |
|---|---|
inputRef | Ref<HTMLInputElement | null> |
Theme 테마
export default defineAppConfig({
ui: {
input: {
slots: {
root: 'relative inline-flex items-center',
base: [
'w-full rounded-md border-0 appearance-none placeholder:text-dimmed 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-sm/4 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-sm/4 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-base/5 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-base/5 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 focus:outline-none'
},
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
leading: {
true: ''
},
trailing: {
true: ''
},
loading: {
true: ''
},
highlight: {
true: ''
},
fixed: {
false: ''
},
type: {
file: 'file:me-1.5 file:font-medium file:text-muted file:outline-none'
}
},
compoundVariants: [
{
color: 'primary',
variant: [
'outline',
'subtle'
],
class: 'outline-primary/25 focus-visible:outline-3 focus-visible:ring-primary'
},
{
color: 'primary',
variant: [
'soft',
'ghost'
],
class: 'outline-primary/25 focus-visible:outline-3'
},
{
color: 'primary',
highlight: true,
class: 'ring ring-inset ring-primary'
},
{
color: 'neutral',
variant: [
'outline',
'subtle'
],
class: 'outline-inverted/25 focus-visible:outline-3 focus-visible:ring-inverted'
},
{
color: 'neutral',
variant: [
'soft',
'ghost'
],
class: 'outline-inverted/25 focus-visible:outline-3'
},
{
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'
}
},
{
fixed: false,
size: 'xs',
class: 'md:text-xs'
},
{
fixed: false,
size: 'sm',
class: 'md:text-xs'
},
{
fixed: false,
size: 'md',
class: 'md:text-sm'
},
{
fixed: false,
size: 'lg',
class: 'md:text-sm'
}
],
defaultVariants: {
size: 'md',
color: 'primary',
variant: 'outline'
}
}
}
})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 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-sm/4 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-sm/4 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-base/5 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-base/5 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 focus:outline-none'
},
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
leading: {
true: ''
},
trailing: {
true: ''
},
loading: {
true: ''
},
highlight: {
true: ''
},
fixed: {
false: ''
},
type: {
file: 'file:me-1.5 file:font-medium file:text-muted file:outline-none'
}
},
compoundVariants: [
{
color: 'primary',
variant: [
'outline',
'subtle'
],
class: 'outline-primary/25 focus-visible:outline-3 focus-visible:ring-primary'
},
{
color: 'primary',
variant: [
'soft',
'ghost'
],
class: 'outline-primary/25 focus-visible:outline-3'
},
{
color: 'primary',
highlight: true,
class: 'ring ring-inset ring-primary'
},
{
color: 'neutral',
variant: [
'outline',
'subtle'
],
class: 'outline-inverted/25 focus-visible:outline-3 focus-visible:ring-inverted'
},
{
color: 'neutral',
variant: [
'soft',
'ghost'
],
class: 'outline-inverted/25 focus-visible:outline-3'
},
{
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'
}
},
{
fixed: false,
size: 'xs',
class: 'md:text-xs'
},
{
fixed: false,
size: 'sm',
class: 'md:text-xs'
},
{
fixed: false,
size: 'md',
class: 'md:text-sm'
},
{
fixed: false,
size: 'lg',
class: 'md:text-sm'
}
],
defaultVariants: {
size: 'md',
color: 'primary',
variant: 'outline'
}
}
}
})
]
})