A design system is a change-management system
J05N became useful when every shared decision included a route from existing behavior, evidence of adoption, and a planned end for the old path.
J05N began as a folder named components-new.
The name contained the entire strategy. The old components were inconsistent; I would create new ones. Five personal applications had grown their own buttons, fields, dialogs, status messages, and layout conventions. A shared package would make the next screen coherent.
The package arrived. The existing screens remained existing.
I was both maintainer and first consumer, which made the failure unusually direct. Nobody had rejected J05N. I simply opened an application to fix a feature and decided that adopting the new field or dialog was too much unrelated risk. The package could describe its current API. It could not explain how a live behavior should change, coexist with its replacement, be verified, and finally disappear.
The components were not the system. The system was the process by which a decision moved from one implementation to several maintained consumers without requiring a rewrite or leaving permanent compatibility behind.
Once J05N was designed around that movement, its artifacts changed. The catalogue became one part of a larger structure containing inventories, behavioral contracts, migration adapters, consumer evidence, deprecation stages, and removal receipts.
A design system stores decisions. A change-management system makes those decisions capable of traveling.
The package saw only its own present
The first J05N release could answer familiar questions:
- Which components are exported?
- Which props do they accept?
- Which tokens define color and spacing?
- Which examples demonstrate their states?
- Which package version contains a fix?
It could not answer the questions that determined adoption:
- Where does an older version of this behavior exist?
- Which differences are intentional?
- What else changes when one consumer migrates?
- Can old and new behavior coexist on the same route?
- How will I know the old path is unused?
- What happens if the migration reveals a regression?
The package treated every consumer as a potential future import. The applications treated J05N as one dependency among state models, CSS, tests, browser support, and domain behavior.
That mismatch explains why catalogue quality did not produce migration. The consumer's present contained history. J05N's present began at installation.
We added a consumer map for each candidate contract. The map named existing instances, their behavior, their source ownership, and the known effort to move. It was not generated from import counts alone; old behavior often lived under unrelated component names or inline route code.
The design-system record began before the shared implementation and extended after it. That longer timeline was the actual system.
A change had a lifecycle
J05N modeled a shared decision through explicit stages:
observed
-> proposed
-> experimental
-> supported
-> preferred
-> old path deprecated
-> old path retiredAn observed pattern existed in the applications but had not earned centralization. A proposal cited its consumers and contract. An experimental implementation could change while tested in a synthetic fixture or one low-risk route. Supported meant the behavior, accessibility expectations, and maintenance owner were clear. Preferred meant new compatible work should use it.
Deprecation did not begin merely because J05N preferred something. It began when a credible migration path existed for each known old consumer. Retirement happened only after evidence showed that the old path no longer ran.
The lifecycle prevented two common distortions. First, an experimental component did not become stable because it appeared in a polished catalogue. Second, a deprecated API did not become removable because a date had passed while consumers remained stranded.
Each transition required different evidence. The system could therefore move slowly at the dangerous edges without pretending the entire component was either unfinished or permanent.
This was the first time J05N represented time as part of interface architecture.
The first library defined components through props and screenshots. J05N changed to behavioral contracts.
A field contract covered label, optional description, error association, required and disabled semantics, focus presentation, and high-zoom behavior. It did not own value state, validation policy, or form submission unless those responsibilities were deliberately part of a higher-level form pattern.
A dialog contract covered accessible naming, initial focus strategy, containment, dismissal requests, background availability, nesting, and focus return. It did not decide whether deleting a domain object was reversible or whether an unsaved edit could be discarded.
An operation status contract distinguished pending, succeeded, failed, and outcome unknown. It did not decide whether retry was safe without an operation-specific receipt.
Writing contracts first changed review. An implementation could be elegant and still fail because it did not uphold focus return. A visually different application component could conform because it preserved the shared operation meanings. Reuse was no longer limited to identical React trees.
Contracts also defined what remained local. That negative space protected J05N from absorbing domain authority merely because it rendered near the relevant control.
The component became one implementation of a maintained decision rather than the only place the decision could be understood.
Migration was a sequence, not a ticket
“Adopt the new field” sounded like one unit of work. A safe migration contained several independently verifiable steps.
For an M31V settings route, the sequence was:
1. Record current field and form behavior.
2. Correct known accessibility defects in place.
3. Introduce J05N field structure behind an application adapter.
4. Run old and new implementations against the same scenarios.
5. Move one field group while preserving submission ownership.
6. Remove the adapter branch when the route no longer needs it.
7. Delete old styles and tests that protect retired behavior.
8. Record the route as migrated.Fixing defects before structural migration mattered. Otherwise the new component had to choose between preserving a bug and combining a user-facing correction with a large code change. Small preparatory changes made the later diff easier to review.
The adapter was local and temporary. It translated the route's existing event shape into the J05N value contract. It did not make the shared package accept every historical API.
Old and new versions coexisted at the route boundary, not inside one component controlled by a global flag. This allowed a single workflow to move without making every application upgrade together.
The migration document named rollback. If the new field failed in an unexpected browser, the route could return to the old implementation without reversing unrelated package work.
Change became safer when it could pause between valid states.
Compatibility needed an exit
I initially viewed compatibility wrappers as evidence of empathy for existing code. Several quickly became permanent.
A wrapper accepted both busy and loading. Another translated old button variants. A dialog adapter supported both imperative show() calls and controlled open state. Nothing said when these paths would leave.
The wrappers reduced short-term friction and created long-term ambiguity. New code copied old forms because they remained accepted. Tests doubled. A bug fix had to decide whether historical behavior was part of the maintained contract.
J05N required every compatibility layer to record:
old contract
new contract
known consumers
translation limits
owner
introduced version
warning stage
removal condition
latest removal releaseThe removal condition was more useful than the date. “When M31V settings and R7K1 retry controls have migrated” could be verified. A calendar date without consumer evidence only created pressure to remove code while it was still required or postpone indefinitely.
Some translations were unsafe. A link and button could share a visual recipe but should not be converted through a component that guessed semantics. A dialog with broken focus behavior should not receive a wrapper that made its markup appear compliant. In those cases, the migration required application work.
A bridge is valuable because it connects two shores. J05N stopped accepting bridges whose far end was undefined.
Consumer evidence replaced version optimism
All five applications could install the newest J05N package while rendering no J05N components. Package adoption and decision adoption were different states.
We tracked contracts by consumer:
dialog focus contract
M31V delete: migrated
X8B6 edit: local behavior, eligible
C62Y help panel: excluded, not modal
R7K1 log drawer: excluded, non-modal
field relationship contract
M31V settings: migrated
X8B6 transaction: adapter active
T04P filters: baseline form retainedExclusions were as important as migrations. The map explained why a visually similar instance was not a consumer. Without that record, future inventory would repeatedly propose centralizing the same unrelated pattern.
Import analysis supplemented the map. A build rule could prevent new imports from a deprecated module. Runtime development warnings could identify old paths difficult to find statically. Visual and browser tests covered behavior during coexistence.
Retirement required negative evidence: no known consumers, no runtime warnings in the exercised routes, no source references outside documented exceptions, and a successful build after removal.
The system's progress became the disappearance of unsupported variation, not the installation count of its package.
The original catalogue displayed ideal components. J05N kept those examples and added migration fixtures.
A fixture reproduced the surrounding conditions that made adoption difficult: global CSS collisions, long translated labels, nested forms, narrow containers, server errors, slow operations, and older application spacing. It could render old and new implementations beside the same scenario.
This made compatibility visible. A field did not pass merely because its standalone example looked correct. It needed to preserve label association and error behavior inside the kind of form it would replace. A dialog needed to return focus after its invoking list row disappeared. A status component needed to represent unknown outcome without converting it to failure.
Fixtures remained synthetic and contained no production data. Their purpose was not to claim complete application coverage. They shortened the distance between catalogue certainty and consumer reality.
The catalogue also showed deprecation state. An old pattern page linked its replacement, translation limits, remaining consumers, and removal plan. A new example included a “Migrating from” section where history created non-obvious work.
Documentation stopped presenting the system as timeless. It showed decisions in motion.
The first component library treated accessibility as a checklist after visual completion. J05N placed it in the contract because migration could otherwise standardize defects.
Centralizing a dialog did not make every dialog accessible automatically. It created one maintained implementation of focus entry, containment, naming relationships, and return, plus documented choices the application still needed to make.
Centralizing a field made label, description, and error relationships difficult to omit. It did not decide whether validation arrived at a useful time. Centralizing status announcements provided queue and interruption rules. It did not decide which domain state deserved an announcement.
This distinction improved change management. Each migration had mechanical guarantees and manual scenarios. Automated tests checked roles, relationships, focus movement, and stable DOM behavior. Manual checks covered reading order, screen-reader announcement, keyboard flow, zoom, and whether an error was understandable in context.
Accessibility regressions became release consequences, not cosmetic defects. A prop rename was not the most important breaking change if a new focus strategy moved users to the wrong place.
Shared infrastructure paid off when it centralized knowledge and test methods. It became dangerous when its existence was treated as a substitute for applying that knowledge in each task.
Tokens needed migration semantics too
Tokens seemed simpler than components because replacing a color value did not involve event behavior. Their change path still mattered.
The first token set renamed raw values:
blue500
gray700
space12
radius4This improved consistency without clarifying intent. A later change to blue500 affected links, focus indicators, selected states, and primary actions together even when those roles had different contrast and interaction requirements.
J05N introduced semantic roles gradually:
foreground-muted
action-primary-background
focus-indicator
surface-raised
border-dangerMigration could not be a global search from one raw value to one role because the same value served several meanings. The consumer inventory classified each use. Applications could adopt roles route by route while raw aliases remained temporarily available.
CSS custom properties were becoming practical across modern browsers, but my support matrix did not assume every environment had the same capability. Generated CSS emitted a static declaration followed by a custom-property declaration with a fallback. Older browsers received the baseline value; newer ones could change theme mappings at runtime.
The fallback duplicated values during transition. That was acceptable because its removal condition was explicit. Compatibility was temporary infrastructure, not proof that every layer had to remain equally dynamic forever.
Even a color decision needed identity, consumers, and an exit from its old name.
Release notes described consequences
Early J05N release notes looked like package notes:
- Rename `appearance` to `kind`.
- Remove deprecated `busy` prop.
- Change dialog close event.They told a maintainer what changed in source and little about the affected interface.
The revised notes described consequence and path:
Dialog dismissal now reports `escape`, `backdrop`, and explicit-action reasons.
Consumers that discard draft state in `onClose` must decide which reasons permit discard.
The old callback remains through 0.8 with a development warning.
M31V delete and the synthetic dirty-form fixture have migrated.A change could be breaking without a type error. Moving initial focus, changing announcement timing, or altering default spacing might compile while disrupting a task. Conversely, a type-level rename with a safe codemod and identical behavior could be operationally small.
Release review therefore included contract changes, visual changes, migration mechanism, consumer status, and rollback. Semantic versioning remained useful, but the version number summarized a decision rather than replacing its explanation.
I began to think of release notes as instructions for moving state across repositories and behavior across time.
API cleanliness can make deletion feel inherently virtuous. J05N learned to distinguish deletion that reduces ambiguity from deletion that merely transfers work to consumers.
A breaking proposal answered:
- What user-facing or maintenance problem does the old contract create?
- Can the new behavior coexist long enough for staged migration?
- Which parts can be changed mechanically?
- Which parts require product judgment?
- How will old usage be detected?
- What evidence permits removal?
- What is the rollback if a consumer reveals a missing case?
If the only justification was a nicer prop name, the change often waited. If the old API allowed invalid semantic combinations or inaccessible behavior, change had stronger grounds.
Codemods handled syntax only when the mapping was unambiguous. They did not guess whether an anchor-styled button represented navigation or action. Ambiguous cases received a report requiring review.
Removal happened in its own change after consumers migrated. This kept migration evidence separate from deletion and made rollback less destructive. The old code could be deleted confidently because its absence had already been rehearsed.
Breaking change discipline was not resistance to improvement. It was respect for the state that improvement had to cross.
J05N was a personal project, but contribution still existed. Patterns emerged from different applications and occasional collaborators could suggest corrections. The pressure to add every repeated surface did not require a large company.
A proposal entered the core only when:
- The behavior was genuinely shared rather than merely similar in appearance.
- The contract could be explained without importing a domain model.
- I could maintain its accessibility and test scenarios.
- At least one real migration demonstrated the path.
- Centralization reduced variation I intended to reduce.
Sometimes the result was guidance or a composition recipe rather than code. Sometimes an application-local component used J05N tokens and primitives without becoming a J05N export. Sometimes two projects kept separate implementations until their behavior converged through experience.
Saying no protected both sides. J05N avoided becoming a warehouse of thin domain wrappers. Applications retained authority over behavior the shared package could not responsibly own.
Contribution was therefore not “send a component.” It was “bring evidence of a shared decision and participate in its maintenance path.”
The higher bar reduced catalogue growth and increased the usefulness of what remained.
A personal collection still has limited attention. J05N could consume all available time while the applications it served stopped evolving.
We assigned migration budgets by risk. An accessible defect with broad impact justified immediate work. A token rename with no user consequence could wait until a route changed for another reason. A large component migration needed a clear benefit beyond architectural tidiness.
Routes did not receive unrelated J05N rewrites merely because they were open. The migration plan separated preparatory fixes from feature changes and kept diffs reviewable. When a component needed a deep state-model correction, it moved in an explicit project rather than hiding under visual cleanup.
This prevented design-system work from becoming a permanent modernization tax. J05N existed to lower the cost of coherent change, not to make every application continuously prove allegiance to its newest API.
The budget also encouraged small contracts. Adopting shared field relationships could deliver accessibility value without migrating the form's entire state architecture. Semantic tokens could enter without converting every stylesheet to a new technique.
A system that requires wholesale adoption to provide benefit will remain easiest to demonstrate and hardest to use.
Failure was recorded as system input
The migration map included failed and reversed attempts.
The first M31V form replacement lost focus after validation and was rolled back. A generic Card abstraction was withdrawn after three domain panels revealed incompatible state ownership. A compatibility wrapper that accepted both links and buttons was deleted because it concealed semantics. A visual token consolidation was reversed when one muted role failed contrast on a raised surface.
These were not embarrassing footnotes. They refined contracts and prevented the next migration from repeating the same assumption.
Each failure record named:
consumer and scenario
expected contract
observed break
whether the fault was implementation, abstraction, or migration
correction or reason for withdrawal
other consumers to recheckThis distinguished a buggy component from a wrong shared boundary. Fixing the former could preserve the plan. Discovering the latter required returning ownership to the application or narrowing the abstraction.
A mature system needs a way to say that its preferred path was wrong without leaving every consumer to rediscover why.
The migration receipt became the unit of progress
J05N originally celebrated releases. A version number represented work completed in the package but not value absorbed by applications.
The more meaningful artifact was a migration receipt:
contract: field-relationship v2
consumer: pressroom/settings
previous path: local FieldRow
adapter: removed
scenarios: keyboard, server error, zoom, long label
visual baseline: accepted
old styles: deleted
rollback: previous route commit
reviewed: 2017-05-19The receipt did not need to become a complex service. A structured document in the repository was enough. It linked the consumer, contract version, evidence, and cleanup.
Receipts enabled aggregate questions. Which consumers still depended on a compatibility layer? Which version of the dialog contract did a route implement? Could an old token alias be removed? Which manual scenarios were last exercised?
They also preserved humility. A migrated route was evidence for one context, not proof that the component worked everywhere.
Progress became a chain of observed behavior changes rather than a list of things J05N had published.
The strongest sign that a migration was complete was not that the new component rendered. It was that temporary machinery disappeared.
An application adapter was deleted. The deprecated import failed the build. Duplicate CSS left. Old snapshots and tests that protected retired structure were removed or rewritten around the maintained contract. Documentation stopped teaching the historical path except where a decision record remained useful.
This cleanup had to be planned from the beginning. Otherwise compatibility layers accumulated because removal was never part of adoption work.
We added a “temporary surface” section to each migration proposal. Every wrapper, alias, feature flag, duplicated value, and parallel test appeared there with a removal condition. The migration receipt closed each item.
Deleting the temporary surface reduced more than code size. It restored one source of meaning. Future fixes did not need to choose between old and new branches. New contributors did not learn a transitional API from an adjacent file.
A change-management system succeeds when it can stop managing a completed change.
It would be easy to exaggerate this work into an enterprise design-system story. J05N governed my own growing collection of applications and synthetic migration fixtures. I had no company-wide mandate, broad product organization, or adoption metric to claim.
That constraint made the lesson clearer. Even with one maintainer able to change every repository, existing software resisted a clean destination when the path was vague. History, behavior, browser support, and limited attention created enough organizational reality on their own.
J05N could therefore demonstrate the engineering of change without inventing authority. It tracked consumers I actually controlled. It documented collaboration where it occurred without pretending to command teams. Its success was concrete: old focus behavior retired, field relationships consolidated, semantic roles adopted, adapters deleted, and application-specific components left local for stated reasons.
The system was small enough that every claim could be inspected.
That scope also became a design constraint. J05N avoided processes that only made sense with dedicated staff. A document and build check often served better than a dashboard. Manual accessibility scenarios remained concise enough to run. Release ceremony matched the consequence of the change.
Governance is not the number of people in a room. It is the clarity of who may decide, who bears the cost, and what evidence makes a transition complete.
The system was the ability to change
J05N eventually contained the expected artifacts: semantic tokens, React components, layout recipes, documentation, browser tests, visual comparisons, and release tooling. None of those artifacts alone explains why it began to spread across the projects.
The difference was that each shared decision arrived with history and a future.
The history named current consumers, intentional differences, known defects, and constraints. The contract stated what J05N would maintain and what remained local. The migration path created valid intermediate states. Compatibility had an owner and an exit. Evidence showed where behavior had changed. Retirement removed the path that no longer deserved support.
This approach was slower than filling a catalogue. It was faster than carrying several generations of half-adopted components indefinitely.
The core lesson reached beyond interface systems. Any shared abstraction enters existing software through change. A database schema, deployment protocol, API client, or policy engine can be technically sound and still fail if consumers cannot move safely. The destination and the transition are one design problem.
A component library stores implementation. A design system stores maintained decisions. A change-management system gives those decisions a lifecycle through real software.
The folder stopped being called components-new because newness was no longer meaningful. The useful question became whether an old decision had a safe, observable path to becoming a better one—and whether the system knew when that path was finished.