diff --git a/docs/content/docs/1.getting-started/3.migration/1.v4.md b/docs/content/docs/1.getting-started/3.migration/1.v4.md
index b7a9c58e15..9e2db6e09e 100644
--- a/docs/content/docs/1.getting-started/3.migration/1.v4.md
+++ b/docs/content/docs/1.getting-started/3.migration/1.v4.md
@@ -352,3 +352,24 @@ For more details on AI SDK v5 changes, review the **official AI SDK v5 migration
::tip{to="https://github.com/nuxt/ui/pull/4698" target="_blank"}
View all changes from AI SDK v4 to v5 **in the upgrade PR** for a detailed migration reference.
::
+
+### Updated `modelModifiers` for `UInput` and `UTextarea`
+
+The `modelModifiers` shape used by `UInput` and `UTextarea` has changed in v4:
+
+- The `nullify` modifier was renamed to `nullable` (it converts empty/blank values to `null`).
+- A new `optional` modifier was added (it converts empty/blank values to `undefined`).
+
+Examples:
+
+```diff
+-
++
+```
+
+```diff
+-
++
+```
+
+Use `nullable` when you want empty values as `null`, and `optional` when you prefer `undefined` for absent values.
diff --git a/src/runtime/components/Input.vue b/src/runtime/components/Input.vue
index 43d57ae382..35bcbc1706 100644
--- a/src/runtime/components/Input.vue
+++ b/src/runtime/components/Input.vue
@@ -4,6 +4,7 @@ import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/input'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import type { AvatarProps } from '../types'
+import type { ModelModifiers } from '../types/input'
import type { AcceptableValue } from '../types/utils'
import type { ComponentConfig } from '../types/tv'
@@ -41,13 +42,7 @@ export interface InputProps extends
highlight?: boolean
modelValue?: T
defaultValue?: T
- modelModifiers?: {
- string?: boolean
- number?: boolean
- trim?: boolean
- lazy?: boolean
- nullify?: boolean
- }
+ modelModifiers?: ModelModifiers
class?: any
ui?: Input['slots']
}
@@ -113,7 +108,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.input || {})
const inputRef = ref(null)
// Custom function to handle the v-model properties
-function updateInput(value: string | null) {
+function updateInput(value: string | null | undefined) {
if (props.modelModifiers?.trim) {
value = value?.trim() ?? null
}
@@ -122,10 +117,14 @@ function updateInput(value: string | null) {
value = looseToNumber(value)
}
- if (props.modelModifiers?.nullify) {
+ if (props.modelModifiers?.nullable) {
value ||= null
}
+ if (props.modelModifiers?.optional) {
+ value ||= undefined
+ }
+
modelValue.value = value as T
emitFormInput()
}
diff --git a/src/runtime/components/InputNumber.vue b/src/runtime/components/InputNumber.vue
index ec790ff163..4fc119a2c9 100644
--- a/src/runtime/components/InputNumber.vue
+++ b/src/runtime/components/InputNumber.vue
@@ -3,6 +3,7 @@ import type { NumberFieldRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/input-number'
import type { ButtonProps, IconProps } from '../types'
+import type { ModelModifiers } from '../types/input'
import type { ComponentConfig } from '../types/tv'
type InputNumber = ComponentConfig
@@ -53,6 +54,9 @@ export interface InputNumberProps extends Pick
/**
* The locale to use for formatting and parsing numbers.
* @defaultValue UApp.locale.code
@@ -77,7 +81,7 @@ export interface InputNumberSlots {