The Monorepo UI Package Dependency Trap
I was working on my monorepo and noticed something odd. My Epicenter app needed to install bits-ui
and @lucide/svelte
even though I was already importing these through my shared UI package.
Wait, shouldn’t the UI package provide these dependencies?
Here’s what I found: the UI package had these dependencies in devDependencies
instead of dependencies
. That’s the trap.
The Problem
When you structure a UI package like this:
{
"name": "@repo/ui",
"devDependencies": {
"bits-ui": "catalog:",
"@lucide/svelte": "^0.525.0",
"clsx": "catalog:",
"tailwind-merge": "catalog:"
}
}
Those packages are only installed when developing the UI package itself. Apps that consume @repo/ui
don’t get them.
So every app ends up looking like this:
{
"name": "my-app",
"dependencies": {
"@repo/ui": "workspace:*",
"bits-ui": "catalog:", // Duplicate!
"@lucide/svelte": "^0.525.0" // Duplicate!
}
}
The Fix
Move runtime dependencies to dependencies
in your UI package:
{
"name": "@repo/ui",
"dependencies": {
"bits-ui": "catalog:",
"@lucide/svelte": "^0.525.0",
"clsx": "catalog:",
"tailwind-merge": "catalog:"
},
"devDependencies": {
// Only build tools and types here
"typescript": "^5.7.3",
"svelte-check": "catalog:"
}
}
Now your apps can be cleaner:
{
"name": "my-app",
"dependencies": {
"@repo/ui": "workspace:*"
// That's it! UI deps come along automatically
}
}
The Lesson
In monorepo UI packages, ask yourself: “Does the consuming app need this at runtime?”
If yes → dependencies
If no → devDependencies
Common runtime dependencies for UI packages:
- Component libraries (bits-ui, radix-ui)
- Icon libraries (@lucide/svelte)
- Runtime utilities (clsx, tailwind-merge)
- Styling tools used at runtime (tailwind-variants)
Common dev dependencies:
- Type definitions (@types/*)
- Build tools (vite, rollup)
- Linters and formatters
- Testing libraries
I had 3 apps all installing the same UI dependencies. Now they just install @repo/ui
and get everything they need. Less duplication, clearer dependency tree, easier maintenance.
The fix took 2 minutes. Finding the problem took me way longer than I’d like to admit.