Building a zero-dependency React component library in TypeScript.

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:

  • Buttonvariant, size, disabled, loading; primary / secondary / danger, with a spinner state.
  • Inputlabel, error, type; proper validation states and dark mode.
  • Modalopen, onClose, title; portal rendering, Escape to close, backdrop click.
  • Toastmessage, 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:hidden and 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.

Related
→ React UI Kit on GitHub → Building a browser FPS from scratch → More projects on the portfolio
Why build a React component library with zero dependencies?

Every runtime dependency is shipped into every app that installs your library, plus a version you must keep compatible. Building from raw React primitives and CSS custom properties keeps the bundle tiny and hands styling control to the consumer.

How do you theme components without a CSS framework?

With CSS custom properties as design tokens. Components reference variables like a primary-colour token instead of hard-coded values, so an app re-themes everything — dark mode included — by overriding a few variables.

How should a Modal be implemented in React?

Render it through a portal so it escapes parent overflow and stacking contexts, close it on Escape and backdrop click, and manage focus. That combination is what makes a modal behave correctly, not just look correct.