DEV Community

Cover image for I shipped my first npm package with AI — and it's already in production
Artemy
Artemy

Posted on

I shipped my first npm package with AI — and it's already in production

I'm a frontend developer with about three years of experience. Until a few months ago, "publish an npm package" lived on my someday list — the kind of thing you assume requires a deeper relationship with build tooling than you actually have. Then I built one. It's called daterly, it's a React date picker, and it's already running in internal projects at the company I work for.

The twist: I wrote most of it with AI — specifically Claude Code and the wider Claude toolset. This is the honest version of how that went. Not "AI built my startup overnight," but a real account of where the AI carried me, where it slowed me down, and what I'd actually keep from the experience.

Why build another date picker

The internet does not need another React date picker. I know. But we needed this one.

At work (sima-land) we lean on react-hook-form everywhere. It's the backbone of how we handle forms across projects. The date picker we were using — react-datepicker — kept getting in the way of that. Two things drove us up the wall:

  1. Styling was a fight. Overriding its look to match our design system always felt like wrestling specificity rather than setting a few variables.
  2. The react-hook-form integration was awkward. Wiring it into RHF cleanly took more glue code than it should have, every single time.

When the same papercut shows up in project after project, it stops being a papercut. We use one tech stack on purpose — a shared, repeatable way of building things so any of us can move between projects without relearning the basics. A date picker that fought both our styling approach and our form library was a crack in that consistency.

So the real motivation wasn't "wouldn't it be cool to publish a package." It was: we want one tool that speaks our stack fluently. First-class RHF support, styling that bends without a struggle, and the locale/format behavior we actually need (Russian market: ru locale, dd.MM.yyyy by default). That's the takeaway I keep coming back to — the package exists to protect a way of working, not to chase npm downloads.

What it ended up being

daterly is built on react-day-picker v9 (headless) and date-fns v4. The headline features:

  • Single date and range picking, controlled or uncontrolled
  • A masked text input so people can type a date instead of only clicking a calendar
  • Arbitrary locales and formats via props — the input mask regenerates itself from the dateFormat you pass
  • Optional time selection
  • Zero-overhead react-hook-form integration via a separate daterly/rhf entry point — if you don't use RHF, you don't pay for it
  • Styling through --daterly-* CSS variables and data-* state attributes, so you can theme it without touching JavaScript

The RHF wrappers are generic over your form's value type, so name gets autocomplete and type-safety:

import { RHFDatePicker } from 'daterly/rhf';

<RHFDatePicker<BookingFormValues>
  name="checkIn"
  label="Check-in date"
  rules={{ validate: (v) => v !== undefined || 'Pick a date' }}
/>
Enter fullscreen mode Exit fullscreen mode

That snippet is the whole reason the project exists, honestly.

Two daterly import paths: core DatePicker with no form library, and an opt-in daterly/rhf entry point that only enters your bundle when you use it

Building it with AI: the good parts

I worked almost entirely inside Claude Code, with Claude Design for the visual side. The parts where the AI genuinely shone were the fiddly, self-contained algorithms — the stuff that's annoying to write by hand but easy to specify.

The input mask is the best example. We implemented our own digit-only mask instead of pulling in a masking library, and there's real subtlety in it:

  • Rebuild the masked string from raw digits on every keystroke.
  • Restore the cursor position with requestAnimationFrame after React re-renders the input — otherwise the caret jumps to the end and typing feels broken.
  • Skip over separators when you press Backspace on one.
  • Strip non-digits on paste before masking.
  • Validate via a round-trip check: format(parse(masked)) === masked. That elegantly catches things like 32.01.2024 — it parses, but it doesn't format back to the same string, so you know it's invalid.

That round-trip trick is the kind of thing the AI proposed that made me go "oh, that's nice." Describing the edge cases in plain language and getting a working first pass back was a real accelerator. Same with boilerplate-heavy work: dual ESM/CJS build config, TypeScript declaration setup, test scaffolding. Tedious to set up from scratch, quick to generate and then review.
Round-trip validation of the masked date input: 12.05.2026 formats back to itself and is valid, while 32.01.2024 formats to a different string and is rejected

Building it with AI: the not-so-good parts

It wasn't magic, and pretending otherwise would be dishonest.

The limits and the lag. I was on the Pro plan, and I hit usage limits more than once mid-flow — exactly when you've built momentum and don't want to stop. On top of that, it noticeably slowed down on a larger context, which broke the rhythm. If you're going to lean on AI for a real project, budget for the friction of waiting and rationing, not just the writing.

It needs a driver, not a passenger. The AI is great at producing a plausible chunk of code. Whether that chunk is correct, fits the existing patterns, and doesn't quietly reinvent something three files over — that's still on me. The most valuable skill wasn't prompting; it was reading every diff like a code reviewer who doesn't trust the author. A junior who can't evaluate the output would have shipped subtly broken behavior here.
What AI compresses versus what stays on the developer: it handles the mask algorithm, build config, type declarations and boilerplate, while correctness, fit and diff review remain the human's job

From "it works on my machine" to an actual package

This is the part I underestimated, and the part I'm most glad I didn't skip. Going from a working component to something installable and maintainable is a whole second project:

  • Dual ESM + CJS output with proper type declarations, built via tsup.
  • Versioning with Changesets, so every change is intentional and the changelog writes itself.
  • CI, code coverage, and a bundle-size budget (size-limit) so the thing doesn't quietly balloon.
  • Component tests with Playwright and Vitest — a masked input has enough edge cases that you really want them locked down.

None of this is glamorous, but it's the difference between a gist and a package other people can depend on.
The component took days, the packaging took longer: ESM plus CJS build, Changesets versioning, CI and coverage, size-limit budget, Playwright and Vitest tests, docs

The 0.4.0 incident

A small, very human lesson. Early on, I ran the Changesets CLI interactively and — distracted — picked minor instead of patch. The version jumped to 0.4.0 instead of 0.3.1. No undo on a published version; you just live with the gap.

The fix was a process change I now recommend to anyone: don't bump versions interactively. I have it written into the project's instructions for the AI too — create the changeset file by hand, decide the bump deliberately (patch for fixes, minor for new backwards-compatible props, major for breaking changes), and only then publish. A thirty-second mistake taught me more about release discipline than any tutorial.
A distracted interactive version bump jumped daterly from 0.3.0 straight to 0.4.0 instead of the expected 0.3.1 patch, with no undo on a published version

What I'd actually take away

If you're a frontend dev sitting where I was, three things:

  1. Build for your stack, not for the stars. The best reason to make a library is that it removes friction you hit every day. daterly's whole job is to make our standard stack — react-hook-form, our styling approach, our locale defaults — feel native. The npm badge is a side effect.
  2. AI compresses the boring parts, not the judgment. It wrote a lot of correct code fast. It also needed me to catch what it got wrong, ration limited usage, and wait out the slow moments. Treat it as a fast junior pair, not an autopilot.
  3. The packaging is the real work. The component took days. The build config, versioning, CI, tests, and docs took longer — and that's what makes it usable by anyone other than me.

daterly is MIT-licensed and on npm. Code and docs are on GitHub. If you're fighting your date picker's styling or RHF integration, give it a look — and if you build your own thing instead, even better. That's the point.


I'm also open to freelance work and collaboration. If you've got something you're building and want a hand, or you just want to see what else I've made, it's all at artemy.tech.

Top comments (1)

Collapse
 
mateo_ruiz_6992b1fce47843 profile image
Mateo Ruiz

This is a good reminder that AI speeds up implementation, not ownership. Generating the component is only part of the journey turning it into a production-ready package with tests, versioning, CI, documentation, and a stable API is where most of the engineering effort still goes. That's the difference between code that works once and software other teams can confidently depend on.