Build, Buy, or Wrap: Owning the Abstraction Boundary Every non-trivial feature raises the same question: write it yourself, or bring in a library that already does it? Data grids, rich text editors, date pickers, charting — the honest answer is almost never "build it from scratch." But how you bring in a library changes everything about how much that decision costs you later, and there's a third option between build and buy that most teams skip past without realizing it: wrap . The three options, and what each one actually costs Build. Full control, zero external risk, and a real cost: you now own maintenance, edge cases, accessibility, and browser quirks that a mature library already solved for thousands of other users. Reserve this for logic that's actually core to your product — not for solved problems like "render a sortable table." Buy — and use it everywhere, directly. Fast to ship. The trap is invisible at first: every place in your codebase that imports the library's components and passes its config objects directly is now coupled to that library's API. The library's naming, its prop shapes, its breaking changes on major-version bumps — all of it leaks into every file that touches it. Replacing the library later means touching every one of those files. Wrap. Adopt the library, but only inside one module. Expose your own component API to the rest of the app, and translate to the library's shape entirely inside the wrapper. This costs a little more up front — you're writing an adapter — and it pays for that cost the very first time you need to configure something twice, test a consumer without the real library loaded, or swap the library out. What wrapping actually looks like The test of a good wrapper: your callers should never import the third-party library directly, and should never see its config shape. They speak your language; the library is an implementation detail hidden behind one file. In Angular Wrap the third-party component inside your own, translating your inputs into its API internally: In React Same shape — your component owns the props; the library's API is translated inside, and nowhere else: The day this grid needs replacing — a licensing change, a performance ceiling, a maintenance dead-end — the rewrite touches one file. Every caller is unaffected, because none of them ever depended on the library directly. The framework changes the syntax, not the decision. The Trade-off: Is Wrapping Worth the Extra File? Wrapping costs you an adapter file and a bit of translation code you wouldn't need if you just imported the library directly everywhere. Whether that's worth it depends on how many places end up depending on the library, and for how long. The pain, concretely Direct imports feel free right up until the library changes underneath you: v1: One feature needs a data grid. directly in that one file. Fast, simple, done. v2: Three more features need tables. Fastest path: import directly in each, passing its / shape straight through. No wrapper — why bother, it's just a table. v3: Two more teams copy the pattern, because that's what's already in the codebase. Now six feature modules import the library directly and construct its config objects by hand, each slightly differently. v4: A major version bump changes the shape of (a common kind of breaking change in mature libraries). All six modules break at once. QA files six separate bug tickets before anyone connects them to the same root cause, and the fix has to be repeated six times, once per call site, with no shared place to make it once. Nothing about this required carelessness. It's the direct consequence of six files all speaking the vendor's language instead of yours. Side-by-side Direct import, everywhere Wrapped behind your own component --- --- --- A breaking library upgrade Every call site breaks simultaneously One file to update; callers unaffected Testing a consumer Must load the real (often heavy) library Can fake your own thin contract instead Adding a second consumer Copy the library's config shape again Reuse your existing wrapper as-is Swapping the library later Rewrite every call site Rewrite one file Upfront cost None One adapter file, written once The signal to switch The moment you're about to write the second call site for a library — a second file importing it directly — that's the trigger to wrap it, retroactively including the first. A library used in exactly one place, with no realistic second consumer coming, doesn't need this ceremony yet. There's one deliberate exception, and it's worth naming rather than pretending it doesn't exist: for a category of dependency you already know from experience is heavy, versioned aggressively, and destined for multiple consumers — a data grid, a rich text editor, a charting library — wrap at the first call site. You're not waiting for the signal in that case; you're applying a signal you already learned from the last library like it that burned six call sites at once. Start…