J05N
A shared interface foundation organized around accessibility contracts, migration paths, and maintained design decisions.
Project brief
- Period
- 2017
- Status
- Archived
- Focus
- Accessible interface systems
- Constraint
- Shared components had to migrate existing interfaces rather than replace them.
- Result
- Behavior contracts and migration receipts made reuse maintainable.
J05N began in 2017 as a personal attempt to make the interfaces across C62Y, T04P, X8B6, M31V, and R7K1 behave like they had learned from one another.
Reuse had become a source of drift
They had not. Each project contained its own button, field, dialog, status treatment, spacing scale, and focus behavior. Copying a better implementation into the next repository spread markup without spreading maintenance. Fixing keyboard dismissal in one dialog did nothing for the four versions already copied elsewhere. A new color palette could make the pages look related while their loading, validation, and recovery states still contradicted one another.
My first answer was a polished component catalogue called components-new. It showed ideal buttons, inputs, cards, and dialogs in a fresh React application. I wrote documentation, published a package, and expected my older projects to move toward it.
Almost none did. The catalogue optimized for a greenfield screen. Existing applications had server-rendered templates, different React versions or no React at all, established CSS, awkward states, and behavior I could not replace in one pass. The new library was attractive beside the projects and irrelevant inside them.
J05N emerged from that failure. Its designed object was not a component gallery. It was a path by which an existing interface decision could become explicit, accessible, versioned, adopted, and eventually retired.
Catalogue
A better component exists
The ideal implementation lived beside older applications and asked each one to absorb the migration cost later.
Adoption path
A behavior can move safely
The contract, compatibility layer, migration instructions, and removal point travelled with the implementation.
A personal system, not an imaginary organization
J05N covered my own project collection and several small synthetic applications built to test migration. There was no fictional platform department, staff title, or company-wide mandate. I was the maintainer and the first consumer.
That constraint made the adoption problem more revealing. I could not blame a separate team for resisting a good library. If I avoided migrating one of my own applications, the cost or contract was wrong.
I wrote four goals:
- Centralize interaction and accessibility behavior that should remain consistent.
- Let older applications adopt improvements route by route without a rewrite.
- Keep domain-specific workflows in their applications instead of forcing them into universal components.
- Make every breaking change include its migration and removal path.
The project would be judged by migrated behavior, not the number of components available.
Inventory before abstraction
I audited the existing projects before designing another API. For each recurring pattern, I recorded markup, visual variants, keyboard behavior, validation timing, loading state, failure state, browser constraints, and the reason the variation existed.
Some differences were accidental. Three button heights reflected three afternoons, not three product needs. Other differences were meaningful. An X8B6 action that created durable offline intent needed different feedback from an M31V link that navigated to a preview.
The inventory grouped patterns into three classes:
- Shared behavior worth centralizing, such as focus restoration after a dialog closes.
- Shared visual language with domain-owned behavior, such as status treatments.
- Superficially similar domain components that should remain separate.
This prevented J05N from becoming an abstraction magnet. A component entered the core only when centralization reduced a variation I actually intended to remove and when I could maintain its contract.
The inventory also produced a migration map. I knew which repositories used each pattern, which behavior differences would become breaking changes, and where a compatibility wrapper could help.
Contract-first components
Each J05N component began with a behavior contract rather than a screenshot or prop list. The contract covered:
- Semantic role, accessible name, and relationships.
- Keyboard sequence and focus behavior.
- Pointer and touch behavior where it differed.
- Loading, disabled, empty, validation, and failure states.
- Layout ownership and allowed composition.
- Controlled and uncontrolled state boundaries.
- Browser baseline and progressive fallback.
- Differences from known existing implementations.
A dialog was therefore a focus and dismissal contract, not a panel style. It established how focus entered, where it could move, what Escape meant, how background content was treated, and where focus returned after close.
A field owned label, description, and error association. It did not decide when a domain value was invalid. Applications kept domain validation and passed the resulting state through a typed boundary.
A submit control represented idle, pending, succeeded, and failed behavior without assuming that disabled was always the right pending state. X8B6, for example, sometimes needed to allow continued local work while a prior batch synchronized.
The visual implementation followed the contract. That order made design review more precise: a styling change could be evaluated without reopening what keyboard behavior the component promised.
The dialog that justified the system
Dialog implementations across the projects exposed why copying code was insufficient. One trapped focus but failed to restore it. One closed on any Escape press, including while a nested control used Escape. One placed focus on the close button even when the title and first field provided better context. A server-rendered confirmation had no script fallback.
J05N created a small dialog state machine with explicit open reason, initial-focus strategy, dismissal policy, and return target. The component generated the title and description relationships and warned in development when neither existed.
Manual scenarios covered:
- Open and close with keyboard only.
- Open from a trigger that is removed while the dialog is active.
- Encounter a validation error after submit.
- Nest a popup control that consumes Escape.
- Zoom to 200 and 400 percent.
- Use screen-reader navigation through title, description, and fields.
- Run with reduced motion.
Automated checks could catch missing roles and relationships. They could not establish whether focus order made sense or whether an error announcement arrived at the useful moment. J05N documented both layers as part of completion.
The first migrated dialog was intentionally not the easiest. I chose X8B6’s conflict resolution because it included long content, destructive consequences, and a need to preserve context after close. If the component only worked in the catalogue, it was not ready.
The first token file renamed raw values: gray-500, space-12, blue-600. It allowed global replacement and did not explain why a value existed.
J05N added semantic roles such as foreground-muted, surface-raised, border-focus, action-primary, and state-destructive. Component internals used these roles. A theme mapped roles to values.
I resisted encoding every project-specific color as a core token. A role entered the shared set only when several applications needed the same maintained decision. Experimental values stayed local until their meaning stabilized.
CSS custom properties enabled runtime themes in capable browsers. Static fallback values preserved the baseline for older support targets. The output placed the fallback before the custom-property declaration so unsupported browsers received a complete, fixed theme.
Spacing followed a similar rule. Components owned their internal relationships; page layouts owned space between components. A button did not carry an arbitrary external bottom margin. This made components easier to combine and later allowed CSS Grid layouts without overriding hidden internal assumptions.
The token documentation showed contrast relationships and example contexts rather than color swatches alone. Muted text still needed to remain readable. A destructive color did not carry the entire destructive meaning without text and confirmation behavior.
Progressive TypeScript adoption
By 2017, TypeScript had matured enough to become useful across the shared package, while several consuming projects remained JavaScript. Requiring an immediate migration would have repeated the greenfield mistake.
J05N published declarations and used TypeScript internally. JavaScript consumers received runtime development warnings for critical invalid combinations. Typed consumers gained discriminated states where behavior changed substantially.
For example, an asynchronous action could not declare both an idle label and a failure message without supplying a failure state. The type system prevented some impossible combinations, but it did not attempt to encode every accessibility rule.
I introduced TypeScript repository by repository, beginning with package boundaries and data models rather than converting every file. Compatibility layers accepted older prop shapes, translated them to the new contract, and emitted a development warning with a migration link.
The wrappers had owners and removal releases. A compatibility layer without an expiry would become a second permanent API. J05N tracked remaining consumers so deletion was evidence-based rather than aspirational.
One API cleanup renamed several props before the migration tooling was ready. I pinned old versions in two applications and immediately multiplied support work. After that, a breaking release required a codemod or mechanical checklist, a coexistence window, and a tested example in a real consuming project.
CSS Grid support became practical across modern browsers in 2017, and it was tempting to redesign every application around it. J05N treated Grid as a capability layer.
Layout primitives began with a complete block or flex fallback. An @supports query upgraded specific arrangements where two-dimensional control materially improved the content. Older browsers kept the reading order and usable spacing.
The comparison layouts inherited from C62Y were a useful test. Grid aligned labels and values cleanly at wider widths, while the semantic source remained a readable sequence. I did not reorder content visually in a way that made keyboard and reading order diverge.
Visual regression images ran at widths where the fallback and enhanced layout changed. Manual review checked zoom and long content. A component could not claim Grid support simply because the ideal story looked correct in the newest browser.
This progressive path demonstrated the larger J05N idea: a new capability should enter through a maintained boundary, not a synchronized rewrite. The application could gain value before every consumer shared the newest platform.
Migration was a feature
Every widely used component included a consumer inventory, compatibility path, test plan, deprecation window, and definition of completion.
Migrations occurred route by route. Old and new patterns could coexist while one workflow moved. The shared CSS used namespaced layers at first to reduce collisions. Compatibility wrappers translated old markup and state into the J05N contract where doing so remained safe.
A migration checklist focused on behavior:
- Preserve the accessible name and described-by relationships.
- Map domain state to the new component states.
- Verify focus before, during, and after the interaction.
- Exercise empty, pending, failure, and long-content cases.
- Compare layout at the route’s real width constraints.
- Remove old styles and handlers after the route passes.
The checklist was accompanied by small codemods for mechanical import and prop changes. Codemods never claimed to finish behavioral migration. They created a reviewable starting point and left explicit markers where domain decisions were required.
The progress page showed applications and routes still using a deprecated pattern. Because every consumer was my own, it doubled as a queue of promises I had made to myself. A new core release did not count as adoption.
The catalogue evolved into a state laboratory. Each component rendered ordinary, long-content, empty, loading, error, disabled, high-contrast, reduced-motion, and narrow-width cases. Domain examples came from the personal projects instead of placeholder Latin text.
Visual regression testing covered reference states across meaningful width and theme combinations. Small pixel differences were reviewed, not automatically equated with product defects. The test’s job was to make change visible.
Interaction tests verified keyboard sequences and state transitions. Accessibility automation checked structural rules. Manual protocols covered screen-reader announcements, zoom, text spacing, forced colors, and focus visibility.
I kept a “known limitation” story beside ideal examples. If a tooltip did not work well with a particular input mode, hiding that fact from the catalogue would make the library more dangerous. Limitations carried an issue and an expected fallback.
The state laboratory later became useful beyond components. V0M3 and N17Q would use the same idea for workflow and agent states: a system becomes more reviewable when inconvenient states are easy to summon on demand.
Release policy and maintenance
Stable primitives and experimental patterns shipped from separate entry points. An experiment could change quickly without forcing churn in buttons, fields, or layout foundations.
Release notes described user-visible behavior and migration consequences, not just signature changes. A focus-restoration fix was a product change even when the TypeScript props stayed identical. A color adjustment named its contrast implication. A removed fallback named the browser baseline change.
Every core component had a maintenance record: current contract, consuming routes, open limitations, last manual accessibility review, and removal candidates. If I could not maintain a contract, the component moved back to guidance rather than remaining a neglected central package.
Contribution was a personal design exercise because there was no external team, but I still used three questions before centralizing a new pattern:
- Is the behavior genuinely shared?
- Can the core maintain the promised contract?
- Does centralization remove variation I intend to remove?
Sometimes the outcome was a documented recipe. Shared guidance can be more honest than a universal component with dozens of escape hatches.
The original catalogue examples used short English labels that made every component look more stable than it was. I added a content-pressure fixture with long translations, plural forms, right-to-left layout, mixed scripts, and user-generated strings without convenient breaking points.
J05N did not become a translation framework. Components accepted content without constructing sentences from fragments, exposed direction through the document context, and avoided fixed dimensions that assumed English. Icons whose direction carried meaning responded to writing direction; icons that represented a physical object did not flip automatically.
Error summaries and field messages were especially revealing. A component that concatenated “Invalid” with a field label created grammar and announcement problems. The application supplied a complete message, while the component maintained its association and focus behavior.
Date and number presentation stayed at the application boundary because domain context determined precision and timezone. J05N provided layout and semantic slots, not one universal formatter. This kept the shared package from turning culturally specific display choices into technical defaults.
Right-to-left review exposed CSS rules written in physical directions. I gradually moved spacing and alignment toward logical properties where browser support and fallbacks allowed. The migration followed the same capability-layer approach as Grid rather than rewriting the entire style system at once.
Content pressure also improved ordinary English screens. Buttons stopped depending on a single-line height. Dialog titles could wrap without pushing the close control over text. Status layouts handled an explanation longer than the state word. Testing language variation was another way to test whether components represented relationships or a screenshot.
Documentation that could fail a test
The first documentation consisted of examples and prop descriptions. It did not tell me whether a release had violated the intended interaction. I moved critical statements into executable scenarios while keeping human explanations beside them.
A dialog contract, for example, linked each promise to evidence:
- Accessible name: structural test and manual screen-reader check.
- Initial focus: interaction scenario for each opening strategy.
- Focus containment: keyboard sequence with nested controls.
- Dismissal: Escape, close action, and outside interaction policies.
- Return focus: trigger present, trigger removed, and replacement target cases.
The documentation site displayed the current implementation and its test scenario together. A release could not update one while leaving the other silently stale. Known limitations remained prose because not every important judgment could be reduced to an assertion.
I added decision records for choices likely to be reopened: why a tooltip was not used for required information, why field validation remained domain-owned, why layout primitives used composition instead of a large prop matrix, and why compatibility wrappers had removal dates.
The records reduced a personal-maintainer problem I had underestimated. Six months later, I could easily mistake an intentional constraint for an oversight and “simplify” it back into an earlier failure. Durable reasoning was part of maintaining the system, even when the maintainer and consumer were the same person.
components-new was the first and most valuable failure. It assumed adoption would follow quality and documentation. Existing code needed compatibility, sequencing, and a finish line.
I initially performed accessibility review after visual completion. Important interaction decisions were already embedded by then. Moving the behavior contract and manual scenarios ahead of visual polish reduced rework and improved the actual result.
The first tokens were disguised raw values. Semantic roles made themes and component decisions easier to explain without claiming that every value should be global.
One “flexible” field component accepted so many rendering hooks that it no longer guaranteed label or error association. I split it into a smaller accessible core and local composition around it.
The premature breaking release proved that a clean API could create a dirty ecosystem. Additive transitions and migration evidence became release requirements.
J05N moved the challenge from building one correct interface to changing several existing interfaces coherently. The technical package mattered, but the difficult work was defining contracts, identifying true commonality, preserving accessibility, and designing reversible adoption.
The project reused lessons from every predecessor. C62Y contributed capability layers. T04P contributed named states. X8B6 contributed migration respect for persisted state. M31V contributed versioned releases and exact approvals. R7K1 contributed a state laboratory and evidence tied to immutable builds.
The result was a personal interface foundation whose success could be seen in migrated routes and removed old behavior, not catalogue size. Accessibility knowledge became reusable without pretending automation could replace human review.
The next project, Q2F8, would take the contract idea outside visual components. A privacy preference would need more than an accessible control; it would need a versioned receipt, a server-enforced policy, finite retention, deletion, and an audit surface. J05N centralized intended behavior. Q2F8 would test whether a promise could be enforced across the data lifecycle.
What remained application work
J05N did not centralize every domain component. It could not make accessibility automatic. Visual tests detected differences without knowing whether they were harmful. Compatibility wrappers increased package weight during migrations, and older browser fallbacks duplicated some values.
The system covered a personal collection of projects, not the politics or scale of an enterprise design program. That boundary made the case study more precise: it demonstrates maintained contracts and progressive migration without claiming fictional organizational authority.
Technology: React, progressively adopted TypeScript, CSS custom properties with fallbacks, CSS Grid capability layers, component state fixtures, visual regression tests, accessibility automation, manual assistive-technology protocols, and migration codemods.