feat: composable primitive components
This PR adds unstyled composable components through generateUploadPrimitives which returns:
<Root><Dropzone><Button><AllowedContent>
I've also added examples for minimal-appdir, minimal-pagedir, with-clerk-appdir, with-clerk-pagedir. I can add it to all the rest of the react ones if needed.
All these components accept parameters as children like ({ isUploading }) => <></>.
There is no asChild or as={YourComponent} prop at the moment as I'm not sure which one to use.
I haven't implemented the docs either as I'm not sure if the current styling page should be split up.
Summary by CodeRabbit
-
New Features
- Introduced unstyled "primitive" upload components, including Root, Button, Dropzone, AllowedContent, and ClearButton, enabling full customization of upload interfaces.
- Added new upload experiences to example apps and playground, showcasing drag-and-drop, manual and auto upload modes, and allowed file type displays.
- Exposed a function to generate these primitives for use in custom applications.
-
Documentation
- Expanded theming documentation to explain usage and customization of the new primitive components, with detailed examples.
-
Refactor
- Refactored existing upload button and dropzone components to delegate logic to the new primitive components, simplifying their structure and improving flexibility.
-
Chores
- Updated exports and internal utilities to support the new primitive upload system.
⚠️ No Changeset found
Latest commit: 2552d058f3436cfabf268bda41908de07de7ff64
Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.
This PR includes no changesets
When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
The latest updates on your projects. Learn more about Vercel for Git ↗︎
| Name | Status | Preview | Comments | Updated (UTC) |
|---|---|---|---|---|
| docs-uploadthing | ✅ Ready (Inspect) | Visit Preview | 💬 Add feedback | Apr 25, 2025 7:19pm |
1 Skipped Deployment
| Name | Status | Preview | Comments | Updated (UTC) |
|---|---|---|---|---|
| legacy-docs-uploadthing | ⬜️ Ignored (Inspect) | Visit Preview | Apr 25, 2025 7:19pm |
I like how this is looking! I would probably add some basic styling to the examples, to at least identify the component, since right now, it kind of just looks like the page is broken, rather than there is another dropzone:
I've also added examples for
minimal-appdir,minimal-pagedir,with-clerk-appdir,with-clerk-pagedir. I can add it to all the rest of the react ones if needed.
I think this is plenty in the examples, i'd potentially want to add an example with-composable-components that shows off an example of what one could do with them, but I wouldn't block on this.
I haven't implemented the docs either as I'm not sure if the current styling page should be split up.
Tentatively I would say it doesn't need to be split for now, but cc @juliusmarminge -- thoughts?
Tentatively I would say it doesn't need to be split for now, but cc @juliusmarminge -- thoughts?
Kinda feels like it should be split?
Should our default component use these primitives? A lot of code duplication if not?
Kinda feels like it should be split?
How would you want it split? I felt like the sub sections in theming wasn't bad
Should our default component use these primitives? A lot of code duplication if not?
This is a good point, and also makes sure that they stay in sync. I don't see why we couldn't.
How would you want it split? I felt like the sub sections in theming wasn't bad
I guess not, perhaps a leading section with the level of theming possible with links to respective section could be sufficient
Walkthrough
This update introduces a new set of unstyled "primitive" upload components for React, including Root, Button, Dropzone, AllowedContent, and ClearButton. These primitives are exposed through a new generateUploadPrimitives function, allowing developers to build fully custom upload flows with granular control over UI and behavior. Existing styled components (UploadButton, UploadDropzone) are refactored to delegate their internal logic to these primitives, simplifying their implementation. The documentation is expanded to explain the new primitives and provide usage examples. Example apps and playground code are updated to demonstrate and utilize the new primitives.
Changes
| File(s) | Change Summary |
|---|---|
packages/react/src/components/primitive/root.tsxbutton.tsxdropzone.tsxclear-button.tsxallowed-content.tsx |
Introduce primitive upload components: Root, Button, Dropzone, AllowedContent, ClearButton, and supporting context/state logic. |
packages/react/src/components/button.tsxdropzone.tsx |
Refactor UploadButton and UploadDropzone to delegate upload logic and state to primitive components via render prop pattern. |
packages/react/src/components/index.tsxpackages/react/src/index.ts |
Export generateUploadPrimitives, update exports to include new primitives, and adjust dropzone export path. |
packages/react/src/utils/useControllableState.ts |
Add a new hook for controlled/uncontrolled state management. |
docs/src/app/(docs)/concepts/theming/page.mdx |
Expand documentation to cover the new primitive components, with usage examples and explanations. |
examples/minimal-appdir/src/app/page.tsxexamples/minimal-pagedir/src/pages/index.tsxexamples/with-clerk-appdir/src/app/page.tsxexamples/with-clerk-pagesdir/src/pages/index.tsx |
Update example apps to use and demonstrate the new upload primitives in their upload flows. |
playground/app/layout.tsx |
Refactor navigation markup for clarity; not directly related to upload primitives. |
playground/app/primitives/page.tsx |
Add a new page demonstrating a custom uploader built with the new primitives. |
playground/components/text.tsx |
Add reusable text/heading components for playground UI. |
playground/components/uploader.tsx |
Remove old upload helper usage; switch to using primitives via a local module. |
playground/lib/uploadthing.ts |
Add a local bridge module exporting UT primitives, UTButton, and useUploadThing for playground use. |
Sequence Diagram(s)
sequenceDiagram
participant User
participant Root as UT.Root
participant Dropzone as UT.Dropzone
participant Button as UT.Button
participant AllowedContent as UT.AllowedContent
User->>Dropzone: Drag and drop or select file
Dropzone->>Root: Notify files selected
Root->>Button: Enable upload action
User->>Button: Click to start upload
Button->>Root: Trigger upload process
Root->>Dropzone: Update progress/state
Root->>AllowedContent: Show allowed file types
Root->>User: Notify on upload completion
Possibly related PRs
- pingdotgg/uploadthing#980: Fixes internal dropzone event handling and multiple file upload bugs, closely related to the dropzone logic refactored in this PR.
- pingdotgg/uploadthing#1151: Refines upload progress granularity and styling in upload components, which are now built on the primitives introduced here.
Suggested reviewers
- markflorkowski
[!WARNING] There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.
🔧 ESLint
If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
Scope: all 3 workspace projects Progress: resolved 0, reused 0, downloaded 1, added 0 /tmp/eslint/packages/uploadthing: ERR_PNPM_WORKSPACE_PKG_NOT_FOUND In packages/uploadthing: "@uploadthing/mime-types@workspace:*" is in the dependencies but no package named "@uploadthing/mime-types" is present in the workspace
This error happened while installing a direct dependency of /tmp/eslint/packages/uploadthing
Packages found in the workspace:
✨ Finishing Touches
- [ ] 📝 Generate Docstrings
🪧 Tips
Chat
There are 3 ways to chat with CodeRabbit:
- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
I pushed a fix in commit <commit_id>, please review it.Generate unit testing code for this file.Open a follow-up GitHub issue for this discussion.
- Files and specific lines of code (under the "Files changed" tab): Tag
@coderabbitaiin a new review comment at the desired location with your query. Examples:@coderabbitai generate unit testing code for this file.@coderabbitai modularize this function.
- PR comments: Tag
@coderabbitaiin a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.@coderabbitai read src/utils.ts and generate unit testing code.@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.@coderabbitai help me debug CodeRabbit configuration file.
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.
CodeRabbit Commands (Invoked using PR comments)
@coderabbitai pauseto pause the reviews on a PR.@coderabbitai resumeto resume the paused reviews.@coderabbitai reviewto trigger an incremental review. This is useful when automatic reviews are disabled for the repository.@coderabbitai full reviewto do a full review from scratch and review all the files again.@coderabbitai summaryto regenerate the summary of the PR.@coderabbitai generate docstringsto generate docstrings for this PR.@coderabbitai generate sequence diagramto generate a sequence diagram of the changes in this PR.@coderabbitai resolveresolve all the CodeRabbit review comments.@coderabbitai configurationto show the current CodeRabbit configuration for the repository.@coderabbitai helpto get help.
Other keywords and placeholders
- Add
@coderabbitai ignoreanywhere in the PR description to prevent this PR from being reviewed. - Add
@coderabbitai summaryto generate the high-level summary at a specific location in the PR description. - Add
@coderabbitaianywhere in the PR title to generate the title automatically.
CodeRabbit Configuration File (.coderabbit.yaml)
- You can programmatically configure CodeRabbit by adding a
.coderabbit.yamlfile to the root of your repository. - Please see the configuration documentation for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation:
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
Documentation and Community
- Visit our Documentation for detailed information on how to use CodeRabbit.
- Join our Discord Community to get help, request features, and share feedback.
- Follow us on X/Twitter for updates and announcements.
I'm currently refactoring the prebuilt components to use the primitives. For the dropzone, you can get the isDragActive property through the children as a parameter or if you want to change a className based on it you use the data-drag attribute (ex: data-[drag]:bg-blue-500/30). In the current prebuilt button style API the isDragActive property is passed a parameter for the class but we can't access it.
Exmaple:
<Primitive.Dropzone
className={cn(
"data-[drag]:bg-blue-600/10",
className,
// isDragActive isn't defined here
styleFieldToClassName(appearance?.container, { isDragActive }),
)}
// or here
style={styleFieldToCssObject(appearance?.container, { isDragActive )}
>
{({ dropzone: { isDragActive } }) => /* we have it here */}
</Primitive.Dropzone>
Should the className and style property accept a parameter like?
<Primitive.Dropzone className={({ dropzone: { isDragActive }) => "your class"} />
This feels a bit hacky imo as it still means you can't use isDragActive to compute any other property (unless we did an Object.entries on the props and made a ts helper to convert all the normal attributes to accept functions too). Another way is with a controlled parameter? Imo this feels better.
<Primitive.Dropzone
dropzone={dropzone}
onDropzoneChange={setDropzone}
className={`${dropzone.isDragActive ? "bg-blue-500/10" : ""`}
/>
cc @juliusmarminge
We hope to be able to get this in a minor release, meaning no breaking changes so
styleFieldToClassName($props.appearance?.container, styleFieldArg) should if possible work as before.
Can we not just wrap in another div or something?
<Primitive.Dropzone>
{({ dropzone: { isDragActive } }) => (
<div
className={cn(
"data-[drag]:bg-blue-600/10",
className,
// isDragActive isn't defined here
styleFieldToClassName(appearance?.container, { isDragActive }),
)}
// or here
style={styleFieldToCssObject(appearance?.container, { isDragActive )}
>
...
</div>
</Primitive.Dropzone>
Still needs to be implemented in the docs, but I just want to make sure the public facing api is all good first.
Still needs to be implemented in the docs, but I just want to make sure the public facing api is all good first.
cc @juliusmarminge
FYI - Just merged #980
Hi, I'm quite busy at the moment but I'm planning to get this PR ready with documentation by next week.
Sorry this took so long. I've fixed all the merge conflicts, but I'm not very experienced with documentation and lack the time at the moment so I've added some extremely basic examples for the docs. Except from that the PR is ready for review.
Just played with this a bit and I love this - found some issues with pasting files. I'll try and have a look at fixing that when i got some time
Thanks! In the meantime, should I fix the linting issues?
[!WARNING] This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite. Learn more
- #947
👈 (View in Graphite) - #1176

main
This stack of pull requests is managed by Graphite. Learn more about stacking.