A design system's peer dependency range should be as wide as the host versions you test in CI, and no wider. A range you never run is a promise you cannot keep. A range that is too narrow forces every consuming app to upgrade the host library on your schedule.
What a peer dependency actually says
The npm documentation describes a peer dependency as a way to express compatibility with a host tool or library without requiring it directly. It gives a plugin style example where a package declares it works with one major line of a host. Since npm 7, peer dependencies are installed by default, and a conflicting peer is treated as an install failure unless the legacy peer setting is on.
For a shared component library, the host is usually the UI framework, and sometimes a router or a styling runtime. If the library and the app each carried their own copy, they could disagree about shared state. That is why these packages are declared as peers rather than regular dependencies.
Three range styles and what they cost
| Range style | What consumers see | Main risk |
|---|---|---|
| Exact or one minor line | Install errors when the app is one version off | Lockstep upgrades, teams reach for the legacy peer flag |
| One major line | Smooth installs inside that major | A new minor in the host can break you before you notice |
| Several major lines | Wide compatibility | Untested combinations, bugs reported as "design system is broken" |
The middle row is a sensible default for most teams. The third row only works if your pipeline proves it.
How to set the range honestly
- List the host versions your consuming apps run today. Your range must include all of them, or you strand a team.
- Add a CI job that installs the lowest version in your range and runs unit and visual tests. Add a second job for the highest.
- Widen the range only when both jobs pass on the new version. Narrow it, with a major release of your library, when you drop support.
- Mark integrations that not every app uses as optional through the peer metadata field. npm does not auto install optional peers.
- Write the range into your release notes whenever it changes, so app teams see it before their update bot opens a pull request.
Why this matters for security updates
When a host library ships a security fix, your range decides how fast apps can take it. If your range excludes the patched version, every app is blocked until you publish a new release. Teams then work around the block with overrides or by forcing installs, which hides the real conflict. See peer dependency conflicts in update pull requests for how to triage that, and npm peer dependencies explained for the basics.
A good habit is a standing job that tries the next host release against your library as soon as it appears. You learn about the break from your own pipeline, before a consumer does.
Safeguard scans dependency manifests and lockfiles for vulnerable packages and can show which of your apps pull in a given version, which helps when you need to know whether a range change reaches production. You can try a free scan at gold.safeguard.sh/scan.
Quick check
Should peer dependencies also be listed as dev dependencies?
Usually yes for a library repo, so your own tests and Storybook have a concrete version to run against. Consumers still get the peer range, not your dev pin.
Is the legacy peer deps flag a fix?
It silences the conflict check, it does not resolve the mismatch. Use it as a short, tracked workaround, not a standing setting.
Sources
- npm package.json documentation: peerDependencies and peerDependenciesMeta
- npm install documentation: legacy peer deps behavior
Last reviewed: 19 October 2026.