A small design-system budget does not force a CTO to ship an improvised interface. It forces a choice about which parts of the interface the company must control. My position: buy accessible component primitives, but own the design tokens, integration layer, and release checks. Buying a complete visual language may look cheaper this quarter, yet it can make the next product change disproportionately expensive.
The cheapest component is expensive if it dictates your product decisions
A purchased kit can make the first screen look finished quickly. The expense appears later if its navigation, forms, and interaction patterns determine what the product team is allowed to change. A digital product strategy may call for a shorter onboarding flow, for example, while the kit assumes a multi-step wizard. The team then either changes the product decision to fit the kit or maintains a fork. Neither cost appears in the purchase price.
That does not make purchased software a mistake. It makes the boundary of the purchase the important decision. I would buy behavior that is difficult to implement and verify well—keyboard interaction, focus management, and accessible menu behavior—while keeping product-specific composition in-house. Radix UI can supply primitives for a React 19 application without requiring the team to adopt its visual identity. Figma variables can hold the design intent; CSS custom properties can express the runtime values. Those two representations need an owner and a review process, because a color changed in one but not the other is two competing specifications.
From Zero to Design System Should a CTO Build or Buy? poses the useful procurement question, but a CTO should also ask which decisions remain reversible after purchase. A low-cost kit is a sound choice when the product can follow its conventions. It is a poor bargain when differentiation depends on interactions that the kit makes awkward to express.
I would not build a proprietary button, dialog, and select library merely to avoid a subscription, because accessibility defects and browser-specific behavior would consume engineering time that the subscription price does not measure. I would also not pay for an enterprise design-system platform before the team can name the components it repeatedly changes. In both cases, the spending precedes the evidence.
Start with a narrow ownership statement: “We own token names, application-specific patterns, and the component adapter; the supplier owns low-level interaction behavior.” The adapter matters because application code should import the team’s Dialog interface rather than a vendor package throughout the codebase. If the supplier changes its API or pricing, one boundary changes instead of every product screen. That is a concrete form of budget control, not an argument for abstraction everywhere.
Buying primitives wins early; building a full system wins only after repetition is measured
Consider two named options. Option A, purchased primitives with an owned wrapper, wins when a small team needs to ship several related flows and has not yet established stable visual patterns. Its costs are supplier dependency, wrapper maintenance, and the time required to verify that the primitives work in the actual application. Option B, a fully in-house component library, wins when repeated product requirements are demonstrably incompatible with available primitives and the company can fund a team to maintain interaction behavior, documentation, and upgrades. Its costs are sustained engineering capacity and a slower path to the first dependable release.
The deciding evidence is repetition, not the CTO’s preference for ownership. For a planning exercise, set a threshold to tune: if the same unusual interaction appears in 3 independently shipped flows, investigate an owned component. One exceptional screen is a product requirement; several independently recurring screens may justify a shared API. The threshold is not an industry rule. It is a prompt to collect examples before creating a library that every future team must learn.
A small team can record those examples in Storybook 8 without treating Storybook as the source of truth. Each story should show a state that is costly to overlook: loading, validation error, empty result, keyboard focus, or constrained width. Storybook makes states reviewable; it does not prove that a component works in a routed application. Playwright should exercise at least one real journey through each important pattern, because an isolated dialog story cannot reveal a focus problem caused by the application shell.
For a pilot, choose 2 flows as a scope limit you can adjust: one common flow and one with a difficult form or overlay. Count the components the flows actually share, the exceptions they require, and the files touched when a token changes. Those observations give the CTO a better purchasing signal than a catalog of available components. A catalog shows supply; the pilot shows fit.
Style Dictionary 4 can transform owned tokens into CSS values if the team needs more than one output format. If it needs only CSS, plain custom properties may cost less to operate because there is no generation step to debug. The proper investment is not the more elaborate toolchain. It is a token contract that developers can change deliberately, review in a diff, and test before release.
A budget design system needs release gates more than polished documentation
Teams often spend their limited budget making components easy to browse, then leave changes cheap to merge and expensive to repair. Reverse that priority. Documentation can begin as a short contribution rule and a few representative Storybook stories. Release checks should begin as soon as multiple screens consume the same token or component, because a shared change can create several regressions at once.
Set a baseline using the application, not a vendor demo. In an initial audit, record the measured number of screens using each component, the time a routine visual change takes, and the failures found after release. Keep the baseline even if it is unflattering; without it, neither a purchase nor an in-house library can be judged against the current workflow. Track lead time for a component change and escaped UI defects separately, because a faster merge that creates more repair work is not a saving.
WCAG 2.2 AA requires a contrast ratio of at least 4.5:1 for normal text in the cases covered by that criterion. The following Node.js 20 check reads a flat tokens.json with color.surface and color.text entries whose $value fields contain six-digit hex colors. Run it in GitHub Actions before publishing a token change:
node --input-type=module <<'JS'
import { readFileSync } from 'node:fs';
const t = JSON.parse(readFileSync('tokens.json', 'utf8'));
const light = t['color.surface'].$value;
const dark = t['color.text'].$value;
const lum = h => {
const rgb = h.slice(1).match(/../g).map(x => parseInt(x, 16) / 255);
return rgb.map(v => v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4)
.reduce((s, v, i) => s + v * [.2126, .7152, .0722][i], 0);
};
const ratio = (Math.max(lum(light), lum(dark)) + .05) /
(Math.min(lum(light), lum(dark)) + .05);
if (ratio < 4.5) process.exit(1);
console.log(`Text contrast ${ratio.toFixed(2)}:1`);
JS
That check protects one named color pair; it does not certify the interface. Add axe-core to automated page tests for detectable accessibility failures, and use Playwright for keyboard paths and application states that a token script cannot inspect. Review visual changes as well: a passing contrast calculation cannot tell whether a destructive button has become visually indistinguishable from a safe action. The point of a modest gate is to catch known, repeatable mistakes before they consume the next sprint.
Do not demand perfect coverage before allowing a component to ship, because that rule can push teams to bypass the shared library entirely. Instead, require a test for the failure mode that made the component worth sharing. A shared dialog needs focus and dismissal checks; a shared form field needs error association and disabled-state checks. This keeps the gate proportional to the risk the team has actually accepted.
The purchasing decision should expire if the product stops benefiting
Design-system procurement should be a time-bound product bet, not a permanent verdict on build versus buy. Digital Product Strategy for Scalable Software Growth provides a useful product-level frame; the design-system budget deserves the same scrutiny as any other investment intended to make future changes easier. If the purchased layer helps engineers implement validated product changes without repeated exceptions, keep it. If every important change requires working around it, the apparent saving has ceased to be a saving.
Write an exit condition into the decision before signing a contract. As a proposed review cadence, inspect the choice after 90 days or after the pilot’s first substantial product change, whichever gives the team meaningful evidence. Compare the time to implement that change with the baseline, note how many vendor components needed wrappers or forks, and ask designers which compromises were forced by the tool rather than chosen for users. The review date is a decision aid, not a claim that every product matures on the same schedule.
Also separate vendor-published numbers from operating costs. A supplier’s published seat price is a quotation for access; it is not a forecast of the hours your team will spend adapting components, reviewing upgrades, or replacing abandoned patterns. Put those hours beside the invoice in the same budget. Conversely, do not count an in-house library as “free” because salaries are already approved. Capacity assigned to component upkeep is capacity unavailable for a product experiment.
A practical purchase agreement therefore needs an export path for tokens, a clear license for shipped code, and a way to pin dependency versions during an upgrade. Use pnpm lockfiles to make the installed package versions reviewable, and use Changesets if the team publishes its wrapper as a versioned package. These controls do not eliminate switching costs; they make the costs visible while switching is still possible. The CTO’s job is to fund that visibility, not to predict every component the company will ever need.
The first budget decision is a two-flow pilot, not a platform contract
Pick two live flows this week and ask an engineer and designer to implement them with purchased primitives behind an owned wrapper. Record the baseline, token exceptions, accessibility failures, and time spent changing a shared decision. Do not authorize a full library or a long platform commitment until that pilot shows which work repeats. A small budget can support a proper decision when it pays for evidence before permanence.


