Input
Utilisation
Utilisez la directive v-model pour contrôler la valeur de l'entrée.
<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
Utilisez la prop type pour changer le type d'entrée. Defaults à text.
Certains types ont été implémentés dans leurs propres composants tels que Checkbox, Radio, [InputNumberxph023/docs/components/input-number) etc. et d'autres ont été stylisés comme file par exemple.
<template>
<UInput type="file" />
</template>Placeholder électronique
Utilisez la prop placeholder pour définir un texte d'espace réservé.
<template>
<UInput placeholder="Search..." />
</template>couleur
Utilisez le prop color pour changer la couleur de la bague lorsque l'entrée est focalisée.
<template>
<UInput color="neutral" highlight placeholder="Search..." />
</template>highlight est utilisé ici pour afficher l'état de mise au point. Il est utilisé en interne lorsqu 'une erreur de validation se produit.Variant
Utilisez le prop variant pour changer la variante de l'entrée.
<template>
<UInput color="neutral" variant="subtle" placeholder="Search..." />
</template>taille
Utilisez le prop size pour modifier la taille de l'entrée.
<template>
<UInput size="xl" placeholder="Search..." />
</template>Icône
Utilisez la prop icon pour afficher un Icon à l'intérieur de l'entrée.
<template>
<UInput icon="i-lucide-search" size="md" variant="outline" placeholder="Search..." />
</template>Utilisez les accessoires leading et trailing pour définir la position de l'icône ou les accessoires leading-icon et trailing-icon pour définir une icône différente pour chaque position.
<template>
<UInput trailing-icon="i-lucide-at-sign" placeholder="Enter your email" size="md" />
</template>Avatars
Utilisez la prop avatar pour afficher un Avatar à l'intérieur de l'entrée.
<template>
<UInput
:avatar="{
src: 'https://github.com/nuxt.png',
loading: 'lazy'
}"
size="md"
variant="outline"
placeholder="Search..."
/>
</template>Chargement
Utilisez le prop loading pour afficher une icône de chargement sur l'entrée.
<template>
<UInput loading placeholder="Search..." />
</template>Loading Icône
Utilisez la prop loading-icon pour personnaliser l'icône de chargement. Par défaut, i-lucide-loader-circle.
<template>
<UInput loading loading-icon="i-lucide-loader" placeholder="Search..." />
</template>app.config.ts sous la touche ui.icons.loading.vite.config.ts sous la touche ui.icons.loading.Désactivé
Utilisez le prop disabled pour désactiver l'entrée.
<template>
<UInput disabled placeholder="Search..." />
</template>Exemples
Avec bouton clair
Vous pouvez mettre un Button à l'intérieur de la fente #trailing pour effacer l'entrée.
<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>
Avec bouton copier
Vous pouvez mettre un Button à l'intérieur de l'emplacement #trailing pour copier la valeur dans le presse-papiers.
<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 mot de passe toggle
Vous pouvez mettre un Button à l'intérieur de l'emplacement #trailing pour basculer la visibilité du mot de passe.
<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 indicateur de force de mot de passe
Vous pouvez utiliser le composant Progress pour afficher l'indicateur de force du mot de passe.
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>
Avec limite de caractère
Vous pouvez utiliser l'emplacement #trailing pour ajouter une limite de caractères à l'entrée.
<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>
With raccourci clavier
Vous pouvez utiliser le composant Kbd à l'intérieur de l'emplacement #trailing pour ajouter un raccourci clavier à l'entrée.
<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>
defineShortcuts pour focaliser l'entrée lorsque la touche: kbd{value="/"} est enfoncée.Avec masque
Il n'y a pas de prise en charge intégrée des masques, mais vous pouvez utiliser des bibliothèques comme maska pour masquer l'entrée.
<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>
Avec étiquette flottante
Vous pouvez utiliser l'emplacement #default pour ajouter une étiquette flottante à l'entrée.
<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>
Dans un FormField
Vous pouvez utiliser l'Entrée dans un composant FormField pour afficher une étiquette, un texte d'aide, un indicateur requis, etc.
<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>
Au sein d'un groupe de champs
Vous pouvez utiliser l'entrée dans un composant FieldGroup pour regrouper plusieurs éléments ensemble.
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 numéro de téléphone entrée
Vous pouvez utiliser l'entrée dans un composant FieldGroup à côté d'un SelectMenu pour créer une entrée de numéro de téléphone avec sélection de code de pays.
<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
| 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; } |
Slots
| 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] |
Exposer
Lorsque vous accédez au composant via une référence de modèle, vous pouvez utiliser les éléments suivants:
| nom | type |
|---|---|
inputRefx{lang="ts-type"} | Ref<HTMLInputElement | null>x{lang="ts-type"} |
Thème
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'
}
}
}
})
]
})