Design systems · UX engineering · Accessibility
I build the layer other teams build on.
Component libraries, token pipelines, and the governance that holds them together once a hundred people start contributing.
- ~75components in the library that grew out of the practice I founded at UWM, adopted across 11 broker-facing products
- 9developers in the UX/UI Community of Practice I founded and chaired, spanning Associate to Senior
- Zerooutstanding findings on the GM BuyPower Card accessibility compliance audits
- 94%graduation rate across four 24-week edX cohorts, supervising up to four teaching assistants each
2026 Self-directed Four brands
Design foundations for a four-brand family
One foundation, four appearances. Forty-six recorded decisions, forty-plus measured contrast pairs per candidate architecture, and a deliberate stop before a single token was written.
The premise, and the failure mode it avoids
Four properties had grown up separately. The obvious move was to unify them under one brand with four colour themes. I rejected that in the first recorded decision, because it is not the goal, it is the failure mode. A single brand wearing four coats reads as a template, and every decision downstream inherits an argument about whose colour wins.
What they actually share is structural: design logic, accessibility standards, interaction patterns, semantic architecture, component anatomy, spacing logic, responsive behaviour, engineering implementation. What they do not share is visual expression. That split produced the constraint the whole project runs on.
Every foundation decision must be defensible as structural rather than stylistic.
It is useful because it is falsifiable. If a proposed rule survives being stated without a value attached, it belongs in the foundation. If it only makes sense with a hex code in it, it belongs to a brand.
What the audit found
I measured what was shipping before drawing anything.
| Signal | Count | Reading |
|---|---|---|
| CSS custom properties | 2 | Both framework defaults. No token layer existed. |
| Typefaces | 1 | No voice distinction of any kind. |
| Ad-hoc type sizes | 10 | No scale. Ten separate decisions. |
| Elements at 11px or smaller | 103 | Legibility floor never set. |
| Hairline borders | 366 | A default, repeated, not a decision. |
That distinction matters more than it sounds. Badly designed implies decisions were made and were wrong; you argue with those. Undesigned means there was nothing to argue with, and the work is to introduce the first decision rather than overturn a bad one.
The surprise: the video pipeline was more disciplined than the website
The automated social-video system declared its colours as real constants in a typed source file. I sampled nine frames of the rendered 1080×1920 output and got those values back, matching within H.264 compression. Its teal constant turned out to be the same value as the live site’s header colour.
So a connection between product and broadcast already existed. It had never been documented or tokenised, and nobody had noticed. That reframed the project: less inventing a system, more finding the one that had grown accidentally and giving it a name.
The same pipeline then failed completely on type. Every one of roughly fifty fontFamily declarations was the generic keyword sans-serif, the host default, in fifty places. Alongside it, eighteen ad-hoc sizes and fifteen letter-spacing values.
The colour constants prove the pipeline can hold a system. Type was simply never given one.
Which produced an unexpectedly liberating conclusion: there is no legacy typeface to preserve. A migration everyone assumed would be expensive cost nothing, because the thing being migrated from did not exist.
The load-bearing colour was not the one anybody would have named
Asked which colour holds this family together, the intuitive answer is the teal. It is the header, the constant, the one that feels like the brand. Measured across every ground the system has to survive, including an illustrated character’s crimson, the answer is the cream.
| Colour | Teal | Deep teal | Cream | Crimson |
|---|---|---|---|---|
| Cream | 8.96 | 11.50 | — | 8.10 |
| Teal | — | — | 8.96 | 1.11 |
| Brand orange | 3.13 | 4.02 | 2.86 | 2.83 |
Cream is the only value that survives every ground. Teal falls to 1.11 on the character ground and disappears. That single row produced a rule the system now runs on: on character grounds all network furniture renders cream, on brand grounds it renders orange. The network stays legible and the brand does not turn into the character.
The brand orange failed every text role it was already holding
| Role in production | Measured | Required | Verdict |
|---|---|---|---|
| Cream text on the orange pill | 2.86 | 4.5 | Fails |
| Orange text on cream | 2.86 | 4.5 | Fails |
| Orange as a non-text mark on cream | 2.86 | 3.0 | Fails |
| Orange as a non-text element on teal | 3.13 | 3.0 | Passes |
The answer was not to change the brand colour. It was to accept that one orange cannot do three jobs, and to split it into three steps that each own exactly one ground. The deep step sits at 4.63 in both directions, the lightest value that reads as text on cream and carries cream text.
Three components caused every failure in the published library
I audited the full published social inventory expecting scattered problems. It was largely conformant. Three recurring components accounted for every single failure: white on orange at 3.13, an orange eyebrow on dark teal at 3.77, and a grey caption at 4.42.
And the fix already existed in the same library. The orange city bar uses dark ink at 5.56 and passes. The category pill uses white at 3.13 and fails. Same ground, two treatments, one of them correct. Nobody had to invent anything. Somebody had to notice.
The finding that made the case for foundation-level accessibility
One sibling brand had assigned three colours to three states, carefully: one for waiting, one for safe with a rescue, one for home. Measured against that brand’s own page ground:
| State | Ratio | Reading |
|---|---|---|
| Waiting, urgent | 4.14 | Fails AA for body text, passes large text and non-text UI, which is why its use as a focus ring is sound |
| Safe with a rescue | 6.96 | Passes |
| Home | 1.41 | Below every threshold, including the 3:1 non-text floor |
A dog reaching a home is the single most important state that brand communicates, and it was invisible. The semantics were thoughtfully assigned and independently unusable. No amount of downstream care could have rescued it, because the meaning was carried by colour alone and that colour could not be seen.
The rule that came out of it governs the whole family: meaning, including urgency, state, success and error, must never depend on colour alone. Every semantic state needs a second perceptual channel.
Type is assigned by communication role, never by heading level
Two typefaces were selected, a serif for the editorial voice and a humanist sans for the product voice. The substantive decision was not which faces. It was what governs their assignment.
The serif is not the heading font and the sans is not the body font. A heading does not take the editorial voice because it happens to be an h1. A utility heading like “Filters” is an h2 in the document outline and renders in the product voice. An editorial pull quote may be a p and render in the editorial voice.
Which forces the token naming. Roles are editorial and product, never heading and body. Element-named tokens would silently re-couple the two systems, and every future contributor would inherit the coupling without knowing they had.
Typography follows communication purpose. Semantic HTML follows document structure and accessibility requirements. Two independent systems, and they must not be conflated.
It is quietly an accessibility argument too. Once type no longer depends on heading level, heading levels are free to describe the document truthfully instead of being bent to achieve a visual result. Most systems collapse the two by naming type styles after heading levels, then spend years fighting the consequences one component at a time.
This page is built that way. Its stylesheet defines no --type-heading and no --type-body, and the headings you have been reading switch voice by role, not by level.
Five levels, and the rule that stops franchises becoming mini-brands
Content on these properties has genuinely different personalities: a documentary adoption post, a satirical segment, a breaking-news treatment, a commerce recommendation. The temptation is to let each become its own little brand. The architecture exists to make that impossible.
| A franchise may override | A franchise may never override |
|---|---|
| Accent selection from the brand palette · ground polarity · density and rhythm · decorative intensity · motion character · photography treatment · character presence | Semantic and system colours · focus behaviour · the meaning of action · minimum type sizes · contrast requirements · non-colour-alone rules · the wordmark · commercial-disclosure treatment |
A franchise is a recipe, a bounded delta, not a mini-brand. That second column is the whole mechanism. It turns “the content changes personality, the interface does not” from a slogan into something a code review can enforce.
The retest, and the answer nobody wanted
Three candidate colour architectures survived to the end, differing only in how far the orange’s authority extends: contained to media, shared with editorial, or promoted to action. I tested all three against seven real content contexts rather than the three I started with, across grounds, ink, action, editorial accent, critical, focus, borders, broadcast furniture, character expression and commerce call-to-action.
The three architectures are numerically indistinguishable.
All three passed everything except the same three rows, and those rows are properties of the shared spine rather than of any architecture. Briefly, an annoying result: the measurement I had built to choose between them declined to choose. It was also the most useful thing the retest produced, because it moved the decision to where it belonged.
| Property | Contained | Shared | Promoted |
|---|---|---|---|
| Minimum action margin | 8.96 | 8.96 | 4.63 |
| Orange carries | Rank, media only | Meaning, editorial | Rank, action |
| New governance rules | Fewest | One | Two |
| Ongoing accessibility burden | Low | Low | Highest. Every action rides the 4.63 margin, and every future hover or tint variant must be re-verified. |
They do not differ on conformance. They differ on margin, meaning and governance. The shared option needs exactly one enforceable sentence to hold: if tapping it does something, it cannot be orange. The promoted option leaves four fewer points of contrast slack for every future variant anyone adds, forever.
Verification, and two numbers that did not survive it
Eighteen of eighteen carried-forward spine claims reproduced exactly. Two other widely repeated figures did not reproduce against any spine value. They had been sampled off card artwork using a teal that existed in neither the code palette nor the spine. Recomputed properly, one got worse and one got better. The direction of both findings stood. The numbers were wrong.
That is why contrast claims in this system must record the ground they were measured against. A ratio without its basis is a rumour with a decimal point.
I also recomputed every colour-difference figure, which had been recorded in CIE76. In CIEDE2000 the gap between the semantic critical colour and the character crimson dropped from 23.7 to 13.6, perceptually about half what the record implied. Nothing broke, because that separation had already been made structural rather than chromatic. But it hardened the constraint: at that distance a large critical fill and a character ground are confusable at a glance, so the critical colour may never appear as a large fill.
Where I stopped, on purpose
Nothing is tokenised. There are no Figma variables. No production code was changed and no brand asset was touched. Colour is not locked, and the recommendation in the record is written as a proposal rather than a decision.
Seventeen questions are still open and named, including wordmark governance across three live variants, whether a production per-city theming system with 144 hex values gets repurposed or the rule amended, and how to reconcile three incompatible illustration styles when three characters are permanently locked in code.
Five foundation values are still undefined, and they are why locking has to wait: no product ink value, no success value anywhere despite it being named in the rules, no assigned focus value, a neutral ramp whose border sits at 1.28, and a corrected caption grey awaiting ratification.
On stopping. Every document in this project carries one of three statuses. Locked recorded, do not reopen without new evidence. Production currently shipping, must be considered, and not automatically a good decision. Exploration a candidate, not approved.
The discipline that keeps those honest is refusing to build past the stated stop point. A foundation tokenised before its ink, success and focus values exist will be reopened three months later by whoever finds the gaps, and they will reopen all of it, not just the broken part.
What I would tell you about this in a room
Scope widened three times, and each widening invalidated a method rather than an answer. Local product, then product plus media, then product, editorial, media and commerce. The early colour work was not wrong. It was scoped to a fraction of the brand. Preserving that sequence is the point. It is the difference between a case study that shows a tidy process and one that shows how foundations actually get built.
I logged my own errors. One document filed the brand orange under “remove, framework default” when the brand genuinely owns an orange and the site was merely using the wrong one. The decision log itself shipped twice with two entries missing, because a cleanup script truncated at the wrong marker. Both are in the record, dated, with the correction. A design system’s credibility is made of exactly this and nothing else.
Disagreements were preserved rather than tidied away. Where my recommendation was not the decision that got made, both are recorded and so is the reasoning. A decision log containing only the winners is a marketing document.
2019–2022 United Wholesale Mortgage UI Developer II
Founding a design system practice after management said no
No UI practice, no headcount, no mandate, and a first answer of no. What I had instead was access to every product team in the building and a reason to talk to them.
The no, and what to do with one
Around October 2019 I proposed that UWM should have a UX/UI practice and a design-system approach. Management said no.
The useful thing about that no was what it was actually about. It was not a judgment on whether the work was worth doing. It was a judgment that nobody had asked for it. So I went and got the ask.
I went to the product teams, multiple teams, close to all of them, and gave a short talk about what a shared UI practice would do for them specifically. Not for the company, and not for design. For them, on their current sprint. They said they wanted it. I came back with demonstrated demand rather than an opinion, and got a yes.
It came with a condition, and the condition is the part I would want a hiring team to hear.
It was not a designated part of my day.
The practice ran in slack time. While waiting for code to deploy. During code freezes. Between tasks. In the group’s own lunch hour, until it had earned enough standing to move. It launched at the end of January 2020.
One agenda item on an early whiteboard reads “new meeting time via Teams, not lunch.” That is a single line about calendaring, and it is also the exact moment an unofficial thing became a sanctioned one.
Building the mandate, then the components
The components were never the hard part. Getting a company with a genuine shortage of UI developers to converge on shared UI was the hard part, and it was almost entirely a distribution problem.
Make the practice findable
We stood up an org-wide mailbox and, more usefully, a UI Code Review Request channel: a standing offer that any team could ask a UI developer to review their code. The channel existed because UI developers were in short supply and hard to locate. Making us easy to find turned scarcity into a reason to come to us rather than a reason to route around us.
That channel did more for consistency than any document. Every review was a conversation about a pattern, held at the moment somebody was actually writing one.
Let people name it
Before launch we ran a company-wide naming contest for the system. It is the single best adoption tactic in my record and it cost nothing. Naming is ownership. A library called the design system belongs to whoever built it. A library people voted a name for belongs to them. That was the last session of the practice I led.
What the system was, structurally
| Layer | Contents |
|---|---|
| Styles | Typography, colour palettes, writing guidelines and tone, each with usage guidance |
| Grid and layout | Spacing on an 8pt grid, breakpoints, responsive behaviour |
| Accessibility | WCAG 2.1, standing as its own first-class layer rather than a note attached to components |
| Patterns | Forms and input variations, tables, list elements, grouped lists and spacing |
| Components | Anatomy, and explicit do and do-not guidance per component |
A boundary I keep, because it matters in an interview. Putting WCAG 2.1 into the architecture as a governed pillar is a design-systems-lead act. It is scoping and standardising accessibility so every component inherits it. It is not the same as hands-on audit and remediation, and I do not present it as such. My screen-reader, axe, contrast and keyboard depth comes from the GM account, below.
Design-to-code parity as a versioning rule
The governance I am proudest of defining is also the least visual. We planned versioning so the Figma library and the Storybook build were pinned to the same release. Figma 1.0 and Storybook 1.0 as one version, not two artefacts drifting on separate clocks with a changelog nobody reads.
Most design-system drift is not caused by bad components. It is caused by a designer and a developer looking at two different versions of the same thing and both being right.
The starter-kit strategy, including the awkward column
The plan covered the obvious contents: components, standards and patterns, Storybook, a backend and API map, Lerna integration, a version-control guide, and a process for handling changes. It also had a column headed, roughly, who is buying this and why. Span of control. Priority. What is in it for me, first. Defining an owner. How the kit gets maintained, not just created.
I would put that column on every design-system proposal I ever write again. A system without a named owner and a maintenance answer is a project, and projects end.
The consistency problem, told honestly
Here is the version of this story I actually like, because it is true and slightly unflattering.
The main client application had around fifteen domain modules, each with its own Redux slice, and many contributors. The mechanism holding it together was explicit pattern reuse: a new reducer would literally open with a comment naming the module it was copied from.
It worked. Consistency was real and you could read it in the source. It also shows precisely the limit of convention-by-copy: nothing enforced it, so drift was a matter of discipline. Every new contributor was one hurry away from starting a sixteenth dialect, and the only thing in the way was whether they happened to look at a neighbour first.
That gap, between a convention that works and a mechanism that holds, is the entire argument for a design system. I got to watch it from the inside for three years.
The feature that carried the migration
My first large project at UWM was the URLA programme: the Fannie Mae and Freddie Mac redesign of the residential loan application, the first rebuild of that form since 1992. It ran 2019 to 2021, and the deadline moved twice while we were building it.
- The integration calls to both GSEs, Fannie Mae’s Day 1 Certainty validation service and Freddie Mac’s asset and income modeler.
- The pending-state experience over those asynchronous round-trips: the interface that has to stay honest and legible while a validation request is outstanding and the answer has not come back. Async state is where most loan-application interfaces quietly lie to the user.
- The assets and income section, twice. Once on the legacy ASP.NET MVC application, then again on the new React and Redux client after the migration.
That last one is the whole modernisation story compressed into one feature I carried across both sides of it. The team touched every page of the form we had and built new ones. I got to build the same section under two entirely different architectures and see exactly what the new one bought us and what it cost.
How to read “both ends of the stack” here. I worked across the stack on this feature, including the .NET service layer, the API contract and the ULAD/MISMO data mapping. The mechanism was mob programming: the entire cross-functional team did every role for about a year. That is real breadth, honestly earned, and it is not the same as being the backend engineer. I claim breadth acquired in a mob rotation, scoped to this feature. I do not claim I owned or architected the platform’s services, and final architecture decisions sat with senior engineers throughout.
Succession, and how it ended
I recruited two co-chairs over the life of the practice. The handover method was the only one I have found that works: I had him do everything himself while I walked him through it. Not shadowing. Doing it, badly at first, with me in the room.
The record of that is almost comic in how mundane it looks. On the practice’s own wiki, the co-chair is editing the agenda a day ago, my last contribution is more than a month earlier, and I am still listed as chair. That gap is not somebody going quiet. That is what a successful handoff looks like, and it happened to leave a paper trail.
The system shipped roughly a week after I left. I led the work through to launch and left days before it landed, which is an unsatisfying sentence to write and an honest one.
On my last day a colleague told me the library that had grown out of the practice had reached approximately seventy-five components used by eleven products, and credited me with starting it. I did not build seventy-five components. I started the thing that did, and I ran it for two and a half years without it ever being part of my job description.
On sourcing. This is written from scratch rather than illustrated with screenshots, deliberately. The primary evidence contains colleagues’ names, proprietary source and borrower-shaped data, none of which belongs on a public website. Where a claim here rests on my recollection rather than a document, I say so in an interview without being asked.
2012–2015 MRM/McCann Digital Project Manager
Accessibility and delivery on a four-brand configurator
Two stories from the same account. One is about compliance holding up under audit. The other is about what happens when the person running a process can read the code it runs on.
The accessibility programme
I led the accessibility programme for the GM BuyPower Card, a Capital One-issued co-brand, and for the GM Vehicle Purchase Program sites. The work had to satisfy both WCAG and the card issuer’s own accessibility compliance standards, a stricter combination than either alone, and it was audited externally.
It passed all compliance audits with no outstanding findings.
This is where my depth is hands-on rather than architectural: screen-reader testing in NVDA, JAWS and VoiceOver, accessible component design, audit and remediation. It is the counterpart to the design-system work, where accessibility appears as a governed layer components inherit. Both matter and they are different skills. Systems work makes accessibility the default. Audit work is how you find out whether the default actually held.
One precision I always volunteer. I was agency-side, on the GM account. I have never worked for Capital One. The card is a Capital One-issued co-brand and its issuer standards are why the compliance bar sat where it did, but the employment relationship was MRM/McCann and General Motors, and I correct anyone who compresses it.
The review cycle, and why it took a developer to fix it
The configurator had to be reviewed for accuracy before release across four brand sites: confirming that the CGI vehicle imagery and the underlying data logic agreed, that the truck you configured was the truck you were shown, in the trim you picked, in the colour you picked.
I ran those reviews in the testing environment with the GM clients and the agency-of-record’s Product Information Specialists, and owned issue tracking in JIRA throughout. The process as I found it cost four meetings per cycle, with multiple teams travelling to our office to sit in a room and look at a build together.
I initiated and coordinated a PREPROD testing environment: a live pre-production build stakeholders could open themselves, from wherever they were, before the review.
| Measure | Before | After |
|---|---|---|
| Meetings per review cycle | 4 | 1 |
| Access | Multiple teams travelling to the office | Live PREPROD access, no travel |
| Stakeholders | 8 to 25 people per brand | Same people, informed in advance |
Nobody assigned this. It was not a process improvement initiative and there was no mandate for it. The reason it happened is embarrassingly simple, and it is the sentence I would most want a hiring manager to hear about how I work.
The team did not understand that we could put it up in a PREPROD environment. But because I was a developer, I did.
That is the whole case for a hands-on person running the process. The bottleneck was not effort, coordination or goodwill. Everyone had those. The bottleneck was that the people organising the reviews could not see a technical option that had been available the entire time. You cannot delegate the noticing.
On these numbers. Four meetings to one, and eight to twenty-five people per brand, are the figures as they were. I do not convert them into hours or dollars saved, because we never measured it that way and an invented figure would be worth less than the real one. The meeting count is the hard number. Everything else about the impact is inference, and I present it as inference.
Why this case is here
Because it is the first instance of something I did not recognise as a pattern until much later.
At MRM I saw a gap in how people worked together and built the mechanism to close it, without being asked and without headcount. Seven years later at UWM I did the same thing: a company with no shared UI practice, no mandate to create one, and an initial no, and the answer was again to build the mechanism rather than request the authority.
Two instances, at different employers, a decade apart, one as a manager with direct reports and one as an individual contributor with none. Design-system roles ask for “influence without authority” almost as a formality. It is the thing I have actually done twice, and both times the lever was the same: understand the technical reality well enough to see the option nobody else in the room can see.
Approach
Four things I believe, all learned the expensive way
Measure it, or do not claim it.
Contrast ratios get copied between documents until nobody knows what ground they were measured against. Recomputing rather than trusting is the cheapest quality gate a design system has, and it is the one most often skipped.
Accessibility is a foundation decision, not a remediation phase.
A colour-coded state at 1.41 against its own ground cannot be rescued downstream. The semantics were good and the state was invisible. That is a foundation failure wearing a component costume.
Name tokens after what they say, not where they sit.
Most systems name type styles after heading levels, which quietly forces visual decisions to follow document structure. Separate them and heading levels stay free to describe the document truthfully. An architecture argument and an accessibility argument at once.
Adoption is the work. The components are the easy part.
A library nobody uses is a folder. The mechanisms that actually moved adoption for me were unglamorous: a code review channel so people could find a UI developer, a shared mailbox, a standing meeting that earned its way out of everyone’s lunch hour, and a naming contest before launch, because naming a thing is how people come to own it.
Existing production is evidence, not truth. Do not promote an implementation into the foundation because it already exists, and do not discard one because a cleaner system can be invented on paper. Working rule, four-brand foundation
This page, audited
The page makes the same claims it asks you to believe
Every value below was computed against this page’s own ground, not chosen by eye. The two rules are separated on purpose: the hairline is decoration and is not allowed to carry state, and anything the eye must resolve uses the heavier one.
ink16.15 · AAink-26.32 · AAaccent8.04 · AApass5.89 · AAfail6.91 · AArule-23.32 · staterule1.28 · decoration only
Pass and fail verdicts in the tables above carry a word and a mark as well as a colour, because a verdict that depends on hue alone is the exact failure this page spends three thousand words describing. Dark mode is a full second palette, not an inversion, and every value in it was measured separately.
Contact
Open to design systems, UX engineering and accessibility roles.
- deerommes@gmail.com
- linkedin.com/in/dee-rommes
- Location
- Missouri, remote across the United States
- Also building
- snouthub.com, a production Next.js, Shopify and Sanity platform I design and build end to end