diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md
index 1ec40a5306..54a03ec83c 100644
--- a/packages/vue/CHANGELOG.md
+++ b/packages/vue/CHANGELOG.md
@@ -6,6 +6,12 @@ description: All notable changes will be documented in this file.
## [Unreleased]
+### Changed
+
+- **Field**: Expose `v-model` through `FieldInput`, `FieldSelect`, and `FieldTextarea`. Use
+ `v-model` at these components instead of `FieldRoot` to maintain value type for each form element,
+ as the type is variable. This is consistent with the React version.
+
### Added
- **Frame (Preview)**: Added `Frame` component for rendering components inside an iframe.
diff --git a/packages/vue/src/components/field/examples/input.vue b/packages/vue/src/components/field/examples/input.vue
index 6d26b1e6aa..a0a65603ab 100644
--- a/packages/vue/src/components/field/examples/input.vue
+++ b/packages/vue/src/components/field/examples/input.vue
@@ -1,11 +1,13 @@
Label
-
+
Some additional Info
Error Info
diff --git a/packages/vue/src/components/field/examples/select.vue b/packages/vue/src/components/field/examples/select.vue
index c25c2790df..ffd600acfc 100644
--- a/packages/vue/src/components/field/examples/select.vue
+++ b/packages/vue/src/components/field/examples/select.vue
@@ -1,11 +1,13 @@
Label
-
+
diff --git a/packages/vue/src/components/field/examples/textarea.vue b/packages/vue/src/components/field/examples/textarea.vue
index 09cac7b580..4fead93dfa 100644
--- a/packages/vue/src/components/field/examples/textarea.vue
+++ b/packages/vue/src/components/field/examples/textarea.vue
@@ -1,11 +1,13 @@
Label
-
+
Some additional Info
Error Info
diff --git a/packages/vue/src/components/field/field-input.vue b/packages/vue/src/components/field/field-input.vue
index 1d5378e27b..ddd757e2a0 100644
--- a/packages/vue/src/components/field/field-input.vue
+++ b/packages/vue/src/components/field/field-input.vue
@@ -8,17 +8,28 @@ export interface FieldInputProps
/**
* @vue-ignore
*/
- InputHTMLAttributes {}
+ Omit {
+ modelValue?: InputHTMLAttributes['value']
+}
-
+ emit('update:modelValue', (event.target as HTMLInputElement).value)"
+ >
+
+
diff --git a/packages/vue/src/components/field/field-select.vue b/packages/vue/src/components/field/field-select.vue
index bb1c444da6..9b1562fff4 100644
--- a/packages/vue/src/components/field/field-select.vue
+++ b/packages/vue/src/components/field/field-select.vue
@@ -8,17 +8,28 @@ export interface FieldSelectProps
/**
* @vue-ignore
*/
- SelectHTMLAttributes {}
+ Omit {
+ modelValue?: SelectHTMLAttributes['value']
+}
-
+ emit('update:modelValue', (event.target as HTMLSelectElement).value)"
+ :as-child="asChild"
+ >
+
+
diff --git a/packages/vue/src/components/field/field-textarea.vue b/packages/vue/src/components/field/field-textarea.vue
index fb1347cfed..dd1206582f 100644
--- a/packages/vue/src/components/field/field-textarea.vue
+++ b/packages/vue/src/components/field/field-textarea.vue
@@ -8,17 +8,27 @@ export interface FieldTextareaProps
/**
* @vue-ignore
*/
- TextareaHTMLAttributes {}
+ Omit {
+ modelValue?: TextareaHTMLAttributes['value']
+}
-
+ emit('update:modelValue', (event.target as HTMLTextAreaElement).value)"
+ :as-child="asChild"
+ >
+
+
diff --git a/packages/vue/src/components/field/field.stories.vue b/packages/vue/src/components/field/field.stories.vue
index 760409af64..88f1144caa 100644
--- a/packages/vue/src/components/field/field.stories.vue
+++ b/packages/vue/src/components/field/field.stories.vue
@@ -1,18 +1,34 @@
+
+ Input value: {{ inputModel }}
+
+
+
+ Selected: Option {{ selectModel }}
+
+
+
+ Textarea value: {{ textareaModel }}
+
+
diff --git a/packages/vue/src/components/field/tests/field.test.tsx b/packages/vue/src/components/field/tests/field.test.tsx
index 95756eab02..f3549176fc 100644
--- a/packages/vue/src/components/field/tests/field.test.tsx
+++ b/packages/vue/src/components/field/tests/field.test.tsx
@@ -52,4 +52,10 @@ describe('Field', () => {
render(ComponentUnderTest, { props: { invalid: false } })
expect(screen.queryByText('Error Info')).not.toBeInTheDocument()
})
+
+ it('should allow input to be controlled', async () => {
+ render(ComponentUnderTest, { props: { modelValue: 'Input is controlled' } })
+
+ expect(screen.getByRole('textbox', { name: /label/i })).toHaveValue('Input is controlled')
+ })
})
diff --git a/packages/vue/src/components/field/tests/field.test.vue b/packages/vue/src/components/field/tests/field.test.vue
index bb9f6681cb..fe075b7d6a 100644
--- a/packages/vue/src/components/field/tests/field.test.vue
+++ b/packages/vue/src/components/field/tests/field.test.vue
@@ -1,11 +1,12 @@
Label
-
+
Some additional Info
Error Info