Peer dependencies — when and how to use them
A peerDependency declares 'I need this, but the host app provides it' — used for shared singletons (React, a router) where multiple copies break things. The host installs one shared version; you don't bundle it. Use a wide version range; the host is responsible for satisfying it.
A peer dependency says: "my package needs this, but the consuming application must provide it — I won't bring my own copy."
The three dependency types
dependencies— your package needs it and brings its own copy. Installed automatically, nested under your package.devDependencies— needed only to build/test your package, not at runtime for consumers.peerDependencies— your package needs it at runtime, but expects the host app to install it and share that single instance. You declare a compatible version range; you do not bundle it.
When to use peerDependencies
Use them for libraries that must be a single shared instance across the whole app:
- React / ReactDOM — the #1 case. Two copies of React → hooks break, context doesn't cross the boundary,
instanceofchecks fail, "Invalid hook call" errors. A React component library must peer-depend on React, not bundle it. - A router, a state library, a styling engine — anything stateful or relying on shared module-level state or singletons.
- Plugins/extensions that must use the same instance as the thing they extend (e.g. an ESLint plugin peer-depends on ESLint).
The general rule: if multiple versions/copies would break things, or it's the host's environment, make it a peer dependency.
How to use them
{
"peerDependencies": {
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
},
"peerDependenciesMeta": {
"react-dom": { "optional": true }
},
"devDependencies": {
"react": "^18.0.0" // also a devDep so YOU can build/test against it
}
}- Use a wide range (
>=17, or^17 || ^18) — you want to be compatible with whatever the host already uses. Too narrow and you cause version conflicts. - Also list it as a devDependency so your own build and tests have it.
peerDependenciesMetamarks some peers optional.- Modern npm (v7+) auto-installs missing peers; older npm/yarn just warn — so document the requirement.
The benefits
- One shared instance — no broken hooks/context, no
instanceoffailures. - No duplication — React isn't bundled three times across three of your libraries.
- Host controls the version — the app picks its React version once.
The framing
"A peer dependency means 'I need this, but the host app provides the single shared copy — I won't bundle my own.' You use it for things that must be one instance across the app: React is the canonical case — two copies break hooks and context. Also routers, state libraries, plugin hosts. You declare a wide version range so you're compatible with whatever the consumer already has, also list it as a devDependency so you can build and test, and document it since older package managers only warn rather than install. The payoff is a single shared instance and no duplication."
Follow-up questions
- •What breaks if a React component library bundles its own React?
- •Why should the peer dependency version range be wide?
- •Why also list a peer dependency as a devDependency?
- •How do modern vs older package managers handle missing peers?
Common mistakes
- •Putting React in dependencies instead of peerDependencies — duplicate React, broken hooks.
- •Too narrow a peer version range, causing conflicts.
- •Forgetting to also add it as a devDependency for your own build/tests.
- •Not documenting the peer requirement.
Performance considerations
- •Peer dependencies prevent the same library being bundled multiple times across your packages — directly reducing the consumer's final bundle size and avoiding duplicate runtime state.
Edge cases
- •Host app has a version outside your declared range.
- •Optional peer dependencies (peerDependenciesMeta).
- •Multiple of your libraries with mismatched peer ranges.
- •Monorepos with hoisting affecting peer resolution.
Real-world examples
- •Every React component library peer-depending on react/react-dom.
- •ESLint plugins peer-depending on ESLint; Babel plugins on @babel/core.