Inspiration

I wanted a media library I could actually drop into any React app without dragging in someone else’s design system or a 300kb bundle. Most existing solutions are either tightly coupled to a UI kit, or feel heavy and slow. My goal was a lightweight, fast, headless React media library that still feels rich: drag-and-drop uploads, inline image editing, and a UI that can match whatever design system the app already uses.

What it does

React Media Library is a headless media library for React that you can mount anywhere:

  • Headless core with a simple provider + hooks API
  • Pluggable UI presets (e.g. Tailwind-first preset)
  • Drag-and-drop uploads and grid browsing
  • Image editor entry point (crop/zoom/etc.)
  • Swappable icon set (Lucide, your own SVGs, or no icons at all)
  • Designed to hit a perfect Lighthouse performance score in a minimal playground

You can use the headless core and build your own UI, or start with the bundled preset and customize from there.

How we built it

I started the project directly in Kiro as a conversation: I described the “headless media library” idea, the need for lightweight performance, and that I eventually wanted a perfect Lighthouse score for the demo.

From there:

  • I turned that initial chat into steering docs inside Kiro: what “headless” means for this project, what tech stack is allowed (React + TypeScript, no huge dependencies), and the performance constraints.
  • Once the direction was clear, I wrote specs for each feature: the provider API, grid behavior, upload flow, and theming/preset layer. Kiro used those specs to generate the initial components and wiring while keeping me in control of the architecture.
  • I wired the library into a tiny Vite playground app and a Storybook setup so I could iterate on both DX and Lighthouse performance. Kiro helped refine the configs (Tailwind, PostCSS, tsup build, exports) while I kept an eye on bundle size and render behavior.

Kiro handled a lot of the repetitive code generation and refactors, while I focused on keeping the library API clean, headless, and fast.

Challenges we ran into

  • Headless but not painful
    Designing an API that’s truly headless (no hardcoded styling or icon library) but still easy to adopt took a few iterations. Passing icon components instead of JSX, and offering presets as separate exports, solved most of this.

  • Optional Lucide without bloating the bundle
    I didn’t want lucide-react to be forced into every consumer’s bundle. The solution was to keep the core completely icon-agnostic and ship a separate presets entry that wires Lucide only if you import it.

  • Lighthouse vs. Storybook overhead
    Running Lighthouse straight on Storybook made scores look worse than they should, because Storybook adds its own runtime. The fix was a dedicated Vite playground app and a CLI script that runs Lighthouse against that minimal shell.

  • Tailwind and content paths in a multi-folder setup
    Making Tailwind pick up classes across the playground and the library src required careful content configuration and making sure Tailwind/PostCSS configs were actually being read (ESM vs CJS gotcha).

Accomplishments that we’re proud of

  • A clean headless API that can be themed with Tailwind, shadcn, MUI, or custom CSS.
  • A default Tailwind preset that looks good out of the box but stays small and unopinionated.
  • Keeping Lucide completely optional, so the core library doesn’t force an icon library.
  • A Vite playground + Lighthouse CLI flow that can reach a 100/100 performance score while still demoing a realistic media grid.
  • Using Kiro effectively: starting from free-form “vibe coding” chat, then locking down behavior in steering docs and specs so the generated code stays aligned with the performance and API constraints.

What we learned

  • Headless design forces you to think very clearly about data vs presentation. Once the boundary is right (provider + hooks + props), presets become much easier to implement.
  • Performance needs to be part of the spec: deciding early what tech is allowed, what dependencies are off-limits, and how Lighthouse will be run kept the project light.
  • Kiro works best when it’s fed structure: starting loose is fine, but the real gains came after I wrote steering docs and concrete specs for each feature. That’s where spec-driven development paid off.
  • Having a dedicated playground app, instead of only Storybook, makes perf tuning and Lighthouse checks feel much closer to real-world usage.

What's next for React Media Library

  • More UI presets (shadcn, MUI, Radix) built on the same headless core.
  • A richer image editor flow (crop, rotate, aspect presets, simple adjustments).
  • Better a11y and keyboard navigation for the grid and selection states.
  • Hooks and adapters for plugging into real backends (Supabase, S3, etc.) while keeping the default demo backend-light.
  • Turning the playground into a public reactkits.dev–style demo hub that showcases the media library alongside other React marketing widgets.

Built With

Share this project:

Updates