Agentic Coding: Bet on the Primitives
A freelance project recently needed three custom charts: a stacked bar chart, a line chart, and a donut. Nothing exotic on paper, but each one had to match the client’s design system exactly. The spacing, the typography, the entrance animations, the way tooltips and legends behave everywhere else in the application. Recharts was already installed in the project. Past-me would have reached for it without a second thought. And here is the ironic part: I learned D3 properly years ago, and precisely because I know how much work hand-rolled charts are, I would never have budgeted them for a client. At least, that was the math before agentic coding.
The Experiment: D3 Primitives vs. Recharts
This time I ran an experiment that would have been irresponsible not long ago: I spiked both approaches within an hour. One set of chart components built on D3’s math primitives, with React rendering the SVG. And a twin set built on Recharts, with the same component API and the same design tokens, so both were interchangeable from the outside.
The result was not what I expected. The primitive version matched the design exactly, entrance animations, crosshair tooltips, legend filtering and all, without fighting anything, because there was nothing to fight. The Recharts version got 80% of the way there faster, but the last 20% required the same custom SVG code anyway: custom shapes, custom tooltips, custom labels. And then it hit a wall. Its entrance animation froze in the project’s React stack, and the only workaround was turning the animation off. The workarounds piled up on the high-level side, not the low-level one.
What an Abstraction Actually Is
That afternoon inverted a rule I had internalized over fifteen years of building for the web: never hand-roll what a library already solves. To understand why the rule is starting to crack, it helps to be precise about what a high-level library actually is.
An abstraction is encoded, prepaid implementation labor. Somebody already spent the thousands of hours it takes to make axis ticks land on round numbers, to make a tooltip follow a cursor without flickering, to make a legend toggle a series. That labor was frozen into a package you can install in seconds. You do not pay for it with money. You pay for it with flexibility: the library’s opinions about rendering, styling, and behavior come bundled with its solutions, and the moment your requirements leave its happy path, you start paying again. In wrapper components, patched styles, prop escape hatches, and GitHub issues that close with “not planned”.
High-level libraries were never free: they traded flexibility for implementation labor, and they won because human implementation time was the scarcest resource in software. When hand-rolled charts cost three developer weeks and a library costs an afternoon of reading docs, accepting the library’s opinions is not a compromise. It is the only rational choice. That math, not elegance, is what pushed every team up the abstraction ladder and kept them there.
Twenty Years Up the Ladder
The history of web development is largely the story of that ladder. jQuery plugins encoded DOM manipulation that nobody wanted to write twice. Backbone, Angular, and React encoded the synchronization of UI and state. Component libraries and design-system kits encoded thousands of design decisions into installable packages. ORMs encoded SQL and schema migrations. create-react-app, and later the meta frameworks, encoded entire build pipelines and routing architectures. Every step was celebrated, rightly, because every step converted expensive human labor into a cheap dependency.
There were early hints that the ladder was not a one-way street. Bootstrap gave way to Tailwind: finished components traded for low-level utilities that let every team build its own look. Shadcn UI pushed further: you do not install its components, you copy them into your project and own them. Both were bets on ownership, placed before ownership got cheap.
The Swing Back Down
Agentic coding is what makes ownership cheap. When an agent can produce a working, reviewed chart component in minutes instead of weeks, the scarcity that justified the high-level library evaporates. And when implementation labor stops being scarce, the rational bet moves back down the abstraction ladder: unopinionated primitives plus a thin custom layer that your team owns, shaped exactly to your use case.
To be clear about what this is not: it is not a return to hand-written everything, and it is certainly not “AI changes everything”. The pendulum swings from renting opinions to owning a layer, not from libraries to assembly.
In my case, the thin layer was a handful of chart components, a few hundred lines each, built on D3’s scale and shape modules, rendered by React, styled with the project’s design tokens from day one. There is no upgrade treadmill, no waiting for a maintainer to support the next React release, no reading someone else’s source to find out why an animation freezes. When the design changes, the team changes the layer. The code reads like the rest of the codebase because it is the rest of the codebase.
Owning a layer is only viable if your team can maintain it, and this is where agentic workflows matter twice: once for writing the layer, and once for keeping it consistent. A custom layer without documented patterns and reviews degrades into a liability like any other unowned code. I wrote about how we handle that side of the equation in Agentic Code Review: Pattern Matching for AI.
Primitives Are Not Zero Dependencies
An objection I want to get ahead of: d3-scale is a library. So are date-fns and zod. Betting on primitives does not mean betting against dependencies.
The distinction that matters is not library versus no library. It is unopinionated versus opinionated: does the dependency compute something for you, or does it decide something for you? A scale maps numbers to pixels and has no opinion about what you draw with them. A charting framework owns the SVG, the animation lifecycle, the tooltip behavior, and hands you configuration props to negotiate with. The first is a primitive, the second is a framework, and the difference shows up exactly at the moment your requirements diverge from the author’s assumptions.
The economics have not changed for genuinely hard, stable problems. Nobody should regenerate date arithmetic, color interpolation, schema validation, or floating-element positioning. Those are solved problems with brutal edge cases, and the primitives that solve them are exactly the dependencies worth keeping. What you want to own is the part that carries your product’s identity: rendering, interaction, behavior.
When the High-Level Library Still Wins
I want to argue against myself for a moment, because this bet is not one-sided.
If your team has no established agentic workflow, the old math still applies in full. Implementation labor has not gotten cheaper for you, and a hand-rolled chart layer is still three weeks of work plus indefinite maintenance. The bet on primitives is a bet on a capability, and you cannot place it before you have the capability.
If your UI is commodity, the library wins even with agents. An internal dashboard with default-styled charts sits squarely on Recharts’ happy path, and the happy path is genuinely excellent. Spending an afternoon rebuilding what a library gives you in ten minutes is not ownership, it is vanity.
Maintenance responsibility is real. Every line you own is a line you maintain. A popular library has thousands of projects surfacing its edge cases and a community fixing them; your custom layer has one project and your team. For anything with deep accessibility requirements, a combobox, a date picker, a modal with correct focus management, mature libraries encode years of screen-reader and keyboard testing that I would not casually regenerate.
And one more honest caveat: my spike worked because I could judge the output. I knew what a scale was, what a shape generator does, what correct looks like. An agent collapses the cost of writing the layer, not the cost of understanding it.
Where the Crossover Sits
Every library decision has always been a bet on where your requirements will land relative to the library’s happy path. The closer your needs sit to that happy path, the more the library wins. The further your needs drift toward custom, the more the primitives win. That heuristic is old and it still holds. Agentic coding did not change the heuristic; it moved the crossover point, dramatically, toward the primitives.
Requirements that used to sit comfortably on the library side of the line, three charts matching a design system, a bespoke form flow, a custom data grid, now sit on the other side, because the cost that kept them there was implementation labor and that cost has collapsed.
Here is the whole argument in one picture. Drag the cost of implementation labor down from hand-written toward agentic and watch the crossover move:
And yes, the chart above is a few hundred lines of React and raw SVG in a folder next to this article’s markdown. No charting library was harmed, or installed.
For twenty years, reaching for the highest sensible abstraction was the safe default, and questioning it was a waste of a client’s budget. The next time a high-level library is already installed and waiting, it is worth asking what it is actually saving you. I suspect the most valuable layer in your codebase a few years from now will not be one you installed, but the thin one your team wrote on top of primitives, because you finally could afford to own it.
