← home

how I built a UI library

May 2026 · 8 min read

I started carabine/ui because I kept rebuilding the same components across every project — a dialog here, a dropdown there, always fighting with accessibility and keyboard navigation. I wanted something that worked out of the box but didn't lock me into a design system I'd eventually fight against.

the core idea

Most component libraries make you choose: either fully styled (opinionated, hard to customize) or fully headless (you own everything, a lot of work). I wanted a middle ground — styled by default with sane defaults, but easy to override or go headless when the design requires it.

The API ended up as: every component ships with default styles, but exposes a unstyled prop and a clean className API so you can restyle from scratch without fighting specificity.

building on Radix

Writing accessible components from scratch — focus trapping, screen reader announcements, keyboard navigation — is genuinely hard and time-consuming to get right. Radix Primitives solved this perfectly: purely behavioral, zero styles. I layered my design system on top of it.

This meant I could ship a fully accessible Dialog, DropdownMenu, and Select on day one, and spend my time on design and API rather than ARIA attributes.

tooling

The build pipeline uses tsup — it compiles to both CJS and ESM with a single config, handles declaration files, and is fast enough that I never think about it. TypeScript strict mode throughout. Tailwind CSS for the default styles.

For testing, I use Vitest + Testing Library. Not extensive coverage yet — mostly smoke tests and interaction tests for the trickier components like combobox.

publishing to npm

The package is scoped under @carabine/ui. Getting the exports field right in package.json was the most painful part — making sure both ESM and CJS consumers get proper types, that tree-shaking works, and that bundlers don't get confused. The key was splitting the exports config carefully and using moduleResolution: bundler in TypeScript.

what I'd do differently

I'd invest in a proper docs site earlier. I built it much later than I should have, and a lot of early friction for potential users came from poor documentation rather than the library itself.

I'd also think harder about the theming story from day one. CSS custom properties work well, but the integration with Tailwind CSS v4 changed the calculus significantly — you can express far more in the theme config now.

carabine/ui is open source. docs at ui.carabine.studio.