Most UI libraries arrive with a pile of peer dependencies and a CSS framework you didn't ask for. I wanted the opposite: a small React component library with zero runtime dependencies, built from raw React primitives and CSS custom properties, fully typed and tested. Here's why that constraint is worth it and what I learned building the kit.
Why zero dependencies?
Every runtime dependency in a component library is a tax paid by every app that installs it. It's bytes in their bundle, a version range you have to keep compatible, and a potential source of breakage you don't control. A styling framework dragged in as a dependency is the worst offender — now your "small" button library ships a megabyte of CSS.
So the rule was: nothing at runtime but React itself. Every component is built from React primitives and styled with CSS custom properties. The result is a library that adds almost nothing to a consumer's bundle and gives them total control over size and look.
The components
The kit covers the pieces you reach for in every app, each with a small, typed props API:
- Button —
variant,size,disabled,loading; primary / secondary / danger, with a spinner state. - Input —
label,error,type; proper validation states and dark mode. - Modal —
open,onClose,title; portal rendering, Escape to close, backdrop click. - Toast —
message,type,duration; auto-dismiss, stacking, success / error / info. - Card — flexible title / content / footer slots.
- Badge — colour-coded status indicators with an optional count.
Theming with design tokens, not a framework
Instead of hard-coding colours, every component references CSS custom properties — design tokens like a primary colour, surface, border and text. A consuming app re-themes the entire kit, dark mode included, by overriding a handful of variables. This is the same technique that powers the light/dark toggle on this very site: flip the variables, and everything that references them changes at once. No theme provider component, no JavaScript re-render — just the cascade doing its job.
The Modal is where the real work lives
A button is easy. A correct modal is the component that separates a real library from a toy one, and it's why I'm happiest with this one:
- Portal rendering so the modal escapes parent
overflow:hiddenand stacking contexts and always sits on top. - Escape key to close, wired up and cleaned up on unmount.
- Backdrop click to dismiss, without closing when you click inside.
Those are the details users never notice when they're right and always notice when they're wrong. Getting them into the library once means never re-solving them per project.
Typed strictly, and tested
The whole thing is TypeScript in strict mode built on React 19, so every prop is typed and autocompletes in the consumer's editor — the API documents itself. It's covered by Jest and React Testing Library unit tests and built with Vite. Tests on a component library aren't optional: they're how you change an internal detail later without quietly breaking everyone who depends on you.
What it taught me
Constraints make better libraries. "Zero dependencies" forced me to actually understand portals, the cascade and focus management instead of importing someone else's answer — and the payoff is a kit that's tiny, themeable and entirely mine to reason about. If you want to really learn React, building your own component library is one of the best exercises there is.