Skip to content

Conversation

@benjamincanac
Copy link
Member

πŸ”— Linked issue

Resolves #5210

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

When using the required attribute on FileUpload, native form validation was not working correctly. Files selected via drag-and-drop would bypass the input element entirely, and the validation state was not properly synced.

The fix ensures dropped files are synced to the native input element using the DataTransfer API in the useFileUpload composable. This allows the browser's native validation to work correctly for both file dialog selection and drag-and-drop, displaying proper localized validation messages.

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 31, 2025

npm i https://pkg.pr.new/@nuxt/ui@5358

commit: 17005f9

Comment on lines +263 to +265
get inputRef() {
return inputRef.value?.$el
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
get inputRef() {
return inputRef.value?.$el
},
inputRef,

The exposed inputRef and dropzoneRef have inconsistent access patterns. inputRef is a getter returning a DOM element directly, while dropzoneRef remains a ref requiring .value access. This breaks backward compatibility and creates an inconsistent API.

View Details

Analysis

FileUpload component has inconsistent ref exposure patterns breaking API consistency

What fails: FileUpload.vue defineExpose exposes inputRef as getter returning DOM element directly, while dropzoneRef remains a ref, creating inconsistent access patterns that break established component API conventions.

How to reproduce:

<script setup>
const fileUpload = ref()
// These access patterns are inconsistent:
fileUpload.value.inputRef.focus()        // Works (direct DOM access)
fileUpload.value.dropzoneRef.value.focus() // Works (ref access)
</script>
<template>
  <UFileUpload ref="fileUpload" />
</template>

Expected behavior: Both refs should be exposed consistently. Example usage in InputKbdExample.vue shows expected pattern: input.value?.inputRef?.focus() which works with Input component but would break with FileUpload's inconsistent exposure.

Result: FileUpload breaks the established pattern used by Input, Textarea, and Select components which all expose refs directly via defineExpose({ inputRef }), while FileUpload uses a getter that bypasses the ref wrapper.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benjamincanac This suggested change makes sense, but it would introduce a breaking change 😬 Seems minor enough to accept it tho?

Copy link
Member Author

@benjamincanac benjamincanac Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it is incorrect, the inputRef is now bound on a Vue component (VisuallyHidden) which adds a layer over binding directly to an <input>: https://vuejs.org/guide/essentials/template-refs.html#ref-on-component. We need to return $el to access the actual input as before.

Comment on lines +263 to +265
get inputRef() {
return inputRef.value?.$el
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benjamincanac This suggested change makes sense, but it would introduce a breaking change 😬 Seems minor enough to accept it tho?

@J-Michalek
Copy link
Contributor

@benjamincanac I found a small a11y issue here: https://github.com/nuxt/ui/blob/v4/src/runtime/components/FileUpload.vue#L326-L337 the div is missing an aria-label when its role attribute is set to button, do you want to handle it in this PR or should I open a different one?

The hidden input should most likely have an aria-label set as well: https://github.com/nuxt/ui/pull/5358/files#diff-99b4a80bd60e93ec9ca72a5fb1ebd1dce084cd3f3baf27c380de7cd60bf38688R370-R382 this is something that is also missing in RekaUI form components: unovue/reka-ui#2251

@benjamincanac
Copy link
Member Author

@J-Michalek For the a11y I guess we can open a new PR, but are you sure we should add an aria-label by default? πŸ€” Isn't it up to the user to do so: <UFileUpload aria-label="..." /> like on other components?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UFileUpload "required" flag shows error when file is selected

3 participants