+
+
+
diff --git a/docs/app/components/content/examples/file-upload/FileUploadFormValidationExample.vue b/docs/app/components/content/examples/file-upload/FileUploadFormValidationExample.vue
new file mode 100644
index 0000000000..314e51ca54
--- /dev/null
+++ b/docs/app/components/content/examples/file-upload/FileUploadFormValidationExample.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/content/3.components/file-upload.md b/docs/content/3.components/file-upload.md
new file mode 100644
index 0000000000..f40a852cb4
--- /dev/null
+++ b/docs/content/3.components/file-upload.md
@@ -0,0 +1,338 @@
+---
+title: FileUpload
+description: 'An input element to upload files.'
+category: form
+links:
+ - label: GitHub
+ icon: i-simple-icons-github
+ to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/FileUpload.vue
+navigation.badge: Soon
+---
+
+## Usage
+
+Use the `v-model` directive to control the value of the FileUpload.
+
+::component-code
+---
+ignore:
+ - modelValue
+ - class
+external:
+ - modelValue
+props:
+ modelValue: null
+ class: 'w-96 min-h-48'
+---
+::
+
+### Multiple
+
+Use the `multiple` prop to allow multiple files to be selected.
+
+::component-code
+---
+ignore:
+ - class
+props:
+ multiple: true
+ class: 'w-96 min-h-48'
+---
+::
+
+### Dropzone
+
+Use the `dropzone` prop to enable/disable the droppable area. Defaults to `true`.
+
+::component-code
+---
+ignore:
+ - class
+props:
+ dropzone: false
+ class: 'w-96 min-h-48'
+---
+::
+
+### Interactive
+
+Use the `interactive` prop to enable/disable the clickable area. Defaults to `true`.
+
+::tip{to="#with-files-bottom-slot"}
+This can be useful when adding a [`Button`](/components/button) component in the `#actions` slot.
+::
+
+::component-code
+---
+ignore:
+ - class
+props:
+ interactive: false
+ class: 'w-96 min-h-48'
+---
+::
+
+### Accept
+
+Use the `accept` prop to specify the allowed file types for the input. Provide a comma-separated list of [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types) or file extensions (e.g., `image/png,application/pdf,.jpg`). Defaults to `*` (all file types).
+
+::component-code
+---
+ignore:
+ - accept
+ - class
+props:
+ accept: 'image/*'
+ class: 'w-96 min-h-48'
+---
+::
+
+### Label
+
+Use the `label` prop to set the label of the FileUpload.
+
+::component-code
+---
+prettier: true
+ignore:
+ - class
+props:
+ label: 'Drop your image here'
+ class: 'w-96 min-h-48'
+---
+::
+
+### Description
+
+Use the `description` prop to set the description of the FileUpload.
+
+::component-code
+---
+prettier: true
+ignore:
+ - label
+ - class
+props:
+ label: 'Drop your image here'
+ description: 'SVG, PNG, JPG or GIF (max. 2MB)'
+ class: 'w-96 min-h-48'
+---
+::
+
+### Icon
+
+Use the `icon` prop to set the icon of the FileUpload. Defaults to `i-lucide-upload`.
+
+::component-code
+---
+prettier: true
+ignore:
+ - label
+ - description
+ - class
+props:
+ icon: 'i-lucide-image'
+ label: 'Drop your image here'
+ description: 'SVG, PNG, JPG or GIF (max. 2MB)'
+ class: 'w-96 min-h-48'
+---
+::
+
+::framework-only
+#nuxt
+:::tip{to="/getting-started/icons/nuxt#theme"}
+You can customize this icon globally in your `app.config.ts` under `ui.icons.upload` key.
+:::
+
+#vue
+:::tip{to="/getting-started/icons/vue#theme"}
+You can customize this icon globally in your `vite.config.ts` under `ui.icons.upload` key.
+:::
+::
+
+### Color
+
+Use the `color` prop to change the color of the FileUpload.
+
+::component-code
+---
+prettier: true
+ignore:
+ - label
+ - description
+ - class
+props:
+ color: neutral
+ highlight: true
+ label: 'Drop your image here'
+ description: 'SVG, PNG, JPG or GIF (max. 2MB)'
+ class: 'w-96 min-h-48'
+---
+::
+
+::note
+The `highlight` prop is used here to show the focus state. It's used internally when a validation error occurs.
+::
+
+### Variant
+
+Use the `variant` prop to change the variant of the FileUpload.
+
+::component-code
+---
+ignore:
+ - class
+props:
+ variant: button
+---
+::
+
+### Size
+
+Use the `size` prop to change the size of the FileUpload.
+
+::component-code
+---
+prettier: true
+ignore:
+ - label
+ - description
+ - class
+props:
+ size: xl
+ variant: area
+ label: 'Drop your image here'
+ description: 'SVG, PNG, JPG or GIF (max. 2MB)'
+---
+::
+
+### Layout
+
+Use the `layout` prop to change how the files are displayed in the FileUpload. Defaults to `grid`.
+
+::warning
+This prop only works when `variant` is `area`.
+::
+
+::component-code
+---
+prettier: true
+ignore:
+ - label
+ - description
+ - multiple
+ - class
+ - ui.base
+props:
+ layout: list
+ multiple: true
+ label: 'Drop your images here'
+ description: 'SVG, PNG, JPG or GIF (max. 2MB)'
+ class: 'w-96'
+ ui:
+ base: 'min-h-48'
+---
+::
+
+### Position
+
+Use the `position` prop to change the position of the files in the FileUpload. Defaults to `outside`.
+
+::warning
+This prop only works when `variant` is `area` and when `layout` is `list`.
+::
+
+::component-code
+---
+prettier: true
+ignore:
+ - label
+ - description
+ - multiple
+ - layout
+ - class
+ - ui.base
+props:
+ position: inside
+ layout: list
+ multiple: true
+ label: 'Drop your images here'
+ description: 'SVG, PNG, JPG or GIF (max. 2MB)'
+ class: 'w-96'
+ ui:
+ base: 'min-h-48'
+---
+::
+
+## Examples
+
+### With Form validation
+
+You can use the FileUpload within a [Form](/components/form) and [FormField](/components/form-field) components to handle validation and error handling.
+
+::component-example
+---
+name: 'file-upload-form-validation-example'
+---
+::
+
+### With default slot
+
+You can use the default slot to make your own FileUpload component.
+
+::component-example
+---
+name: 'file-upload-default-slot-example'
+---
+::
+
+### With files-bottom slot
+
+You can use the `files-bottom` slot to add a [Button](/components/button) under the files list to remove all files for example.
+
+::component-example
+---
+name: 'file-upload-files-bottom-slot-example'
+---
+::
+
+::note{to="#interactive"}
+The `interactive` prop is set to `false` in this example to prevent the default clickable area.
+::
+
+### With files-top slot
+
+You can use the `files-top` slot to add a [Button](/components/button) above the files list to add new files for example.
+
+::component-example
+---
+name: 'file-upload-files-top-slot-example'
+---
+::
+
+## API
+
+### Props
+
+:component-props
+
+### Slots
+
+:component-slots
+
+### Emits
+
+:component-emits
+
+### Expose
+
+When accessing the component via a template ref, you can use the following:
+
+| Name | Type |
+| ---- | ---- |
+| `inputRef`{lang="ts-type"} | `Ref`{lang="ts-type"} |
+| `dropzoneRef`{lang="ts-type"} | `Ref`{lang="ts-type"} |
+
+## Theme
+
+:component-theme
diff --git a/docs/public/components/dark/file-upload.png b/docs/public/components/dark/file-upload.png
new file mode 100644
index 0000000000..6349f7ab85
Binary files /dev/null and b/docs/public/components/dark/file-upload.png differ
diff --git a/docs/public/components/light/file-upload.png b/docs/public/components/light/file-upload.png
new file mode 100644
index 0000000000..dbc78e7973
Binary files /dev/null and b/docs/public/components/light/file-upload.png differ
diff --git a/playground-vue/src/app.vue b/playground-vue/src/app.vue
index cc2e69d572..64285334fa 100644
--- a/playground-vue/src/app.vue
+++ b/playground-vue/src/app.vue
@@ -35,6 +35,7 @@ const components = [
'command-palette',
'drawer',
'dropdown-menu',
+ 'file-upload',
'form',
'form-field',
'input',
diff --git a/playground/app/app.vue b/playground/app/app.vue
index 7348fbd70f..61662537c6 100644
--- a/playground/app/app.vue
+++ b/playground/app/app.vue
@@ -35,6 +35,7 @@ const components = [
'command-palette',
'drawer',
'dropdown-menu',
+ 'file-upload',
'form',
'form-field',
'input',
diff --git a/playground/app/pages/components/file-upload.vue b/playground/app/pages/components/file-upload.vue
new file mode 100644
index 0000000000..b109ba0c77
--- /dev/null
+++ b/playground/app/pages/components/file-upload.vue
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ state.avatar.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Files ({{ files?.length }})
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index 8eebc437f9..04c54f003a 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -12,6 +12,10 @@ export default defineNuxtConfig({
compatibilityDate: '2024-07-09',
+ hub: {
+ blob: true
+ },
+
vite: {
optimizeDeps: {
// prevents reloading page when navigating between components
diff --git a/playground/server/api/blob.put.ts b/playground/server/api/blob.put.ts
new file mode 100644
index 0000000000..cf1a2500a5
--- /dev/null
+++ b/playground/server/api/blob.put.ts
@@ -0,0 +1,12 @@
+export default eventHandler(async (event) => {
+ return hubBlob().handleUpload(event, {
+ formKey: 'files', // read file or files form the `formKey` field of request body (body should be a `FormData` object)
+ multiple: true, // when `true`, the `formKey` field will be an array of `Blob` objects
+ ensure: {
+ types: ['image/jpeg', 'image/png'] // allowed types of the file
+ },
+ put: {
+ addRandomSuffix: true
+ }
+ })
+})
diff --git a/src/runtime/components/FileUpload.vue b/src/runtime/components/FileUpload.vue
new file mode 100644
index 0000000000..992b5bbffc
--- /dev/null
+++ b/src/runtime/components/FileUpload.vue
@@ -0,0 +1,370 @@
+
+
+
+
+
+
+
+
+
+