Vantage Circle · Accessibility & usability
Making an enterprise product
usable, not just consistent.
The gap between what an enterprise product is bought for and what it is like to use every day
A design system settles what things look like. It says nothing about whether a keyboard can reach them, whether a screen reader can name them, or whether a thumb can hit them. Accessibility isn’t a checklist bolted on at the end; it is a signal that the system isn’t doing enough of the thinking for the people using it. This is the other half: a heuristic audit, an exhaustive component run, and the fixes that came out of both, almost none of which had ever been filed as a bug. Fix the screen once, then fix the thing that kept producing it.
| Brief | Self-set, off the back of a marketplace scan: find out why the people who use the product daily rate it far below the people who buy it |
| Role | Design Team Manager; ran the audit, set the severity rubric, made the calls, and shipped the fixes in the codebase |
| Method | Nielsen heuristic audit scored on severity 1–4 and effort S/M/L · one exhaustive component run · fixes shipped behind the guardrails from the design-system project |
| Scope | Four highest-traffic clusters (Recognition, Redemption, Feed, Perks Home) plus the mobile and tablet builds |
| Outcome | Keyboard reachability, assistive-tech semantics, contrast, touch targets and heading structure fixed across web, mobile and tablet. The measurable part comes later: these ship in waves, and the end-user rating is the number to watch. |
01The gap nobody was looking at
Every internal signal said the product was doing fine. The review sites said so too, until you noticed who was writing the reviews.
Enterprise software is bought by one group and used by another. An HR team chooses a recognition platform, configures it, and reviews it on G2 or Capterra. The 3.2M employees who receive recognition through it never touch a review site, except one. So I scanned ten marketplaces and sorted them by who was actually rating.
Buyer-facing marketplaces
4.7 / 5
Ease of use, from 5,122+ reviews. G2, GetApp and Software Advice all agree.
Audience: HR admins and buyers. They configure the programme.
The one end-user rating
2.7 / 5
546 ratings on the US App Store, the only marketplace scoring the people who open the product.
Audience: employees. They receive the programme.
Nothing in that gap is proof of an accessibility problem on its own. Marketplace ratings are noisy, and some of the low reviews had nothing to do with usability. But it reframed the question from “is the product good?” to “good for whom?”, and that is a question an audit can answer.
02An audit that could be argued with
“The app feels clunky” is not a finding. It cannot be assigned, estimated, or disproved.
So the audit ran against Nielsen’s ten heuristics, with each checkpoint written as a concrete platform behaviour rather than a principle: not visibility of system status but the points balance stays visible from catalogue through cart to checkout, not just in the catalogue header. Every checkpoint named the component file it applied to, so a finding arrived pre-located.
Each finding got two scores. Severity 1–4, anchored in real examples so the scale could not drift: a 4 means the user cannot complete the task or their points are at risk; a 1 means it reads as unfinished. Effort S/M/L, anchored the same way, in days. Severity against effort produces a shortlist instead of a wish list.
I scoped it to four clusters (Recognition, Redemption, Feed and Perks Home) for a commercial reason: they carry both the demo path and the daily-driver path. The screens a prospect is shown are the screens an employee lives in. Checkout, profile and account settings were explicitly ruled out of this round and marked for a later one.
03Contrast, when the brand colour is an input
A multi-tenant product cannot promise contrast the way a single-brand product can. Every customer sets their own brand colour, and some of them pick badly.
That is an accessibility constraint disguised as a theming feature, and it has to be solved in the token layer rather than per screen. Four rules carry it, all of them decided during the design-system work and all of them load-bearing here:
Status colours are never tenant-derived. A customer whose brand is red does not get red success states. Green stays green.
Text and surface tokens come from the neutral ramp, not the brand ramp, so legibility does not move when the brand does. If contrast depended on the tenant, it would be untestable; there is no single palette to test.
The type scale has a floor. 11px for anything a user has to read. The 8px badge token exists, and is documented as decoration only, never text.
The tiers that sit on photography stay neutral. Icon-only and overlay buttons ignore the tenant colour, because a brand tint over an image is a contrast lottery.
One audit finding came straight from this. A brand card rendered white text over a gradient, which no contrast tool can evaluate and which produced a different ratio per tenant. Swapping it to a solid fill did not just fix one card; it made the card testable, which is the part that lasts.
04One component, exhaustively
“Make this component better” is the brief that produces unfalsifiable design work. So I built a repeatable process and pointed it at the profile widget.
Ideate exhaustively, score every idea against one rubric, then four gates: the two guardrails from the design-system project, an ROI cut, and a stakeholder eyeball where surviving edits sit committed but unpushed until a human sees them running.
174 ideas ideated and judged. Five shipped. Two raised as global proposals. 167 dropped.
5 shipped2 raised as global proposals167 dropped
Four of the five are invisible. There is no before-and-after screenshot, because the rendered card is pixel-identical; the commits changed attributes, not appearance. That is the finding, not a gap in the evidence: this is work that only exists for people navigating by keyboard or screen reader, which is why none of it had ever been filed. The people filing bugs were using a mouse.
| Idea | The defect, and the change | ROI |
|---|---|---|
| idea-128 | Nominate row was mouse-only, a click handler with no keyboard path + role="link" + tabindex="0" + aria-label | 125 |
| idea-104 | rol="link", a typo, so the role was never applied at all− rol="link" − redundant aria-label | 118.75 |
| idea-22 | Four decorative dash images announced to screen readers alt="dash" → alt="" + aria-hidden="true" | 108 |
| idea-20 | Greetings tile and Get Recognized row fired on click but not on Enter or Space + tabindex="0" + role="button" + keydown | 96 |
| idea-75 | A nested <a> inside another <a>: invalid HTML, undefined behaviourinner anchor removed, label moved outward | 95.3 |
.ts file opened; the scope
guardrail held for all five. ROI is impact-over-effort on the run’s shared rubric; an
invisible keyboard fix scores enormously well on it, which is the bias described below.The rubric had a bias, and finding it changed the workflow
The run surfaced almost entirely accessibility fixes, which looked like a great result until I saw why. An invisible keyboard-operability fix scores enormously well on impact-over-effort; visual craft rarely clears 80. Rank purely on ROI and accessibility wins every run forever, and the visual work never happens. So the workflow gained a focus parameter, and the same 174 ideas produced completely different winners depending on it. A scoring rubric doesn’t remove judgement from design work. It relocates it into the choice of what you are scoring for.
The gate rejected real work, which is how you know it was a gate. Of five visual-pass edits, three were reverted after review and logged as refusals, so nobody re-proposes them in six months.
05The fixes nobody had filed
The profile widget was one component. The audit and the mobile build produced the rest, across three surfaces.
| Keyboard operability (WCAG 2.1.1) | ||
| back-to-top | Scroll control was mouse-only. Made keyboard-operable, given a visible focus ring and a 44×44px minimum target. | #19 |
| consent checkbox | Not reachable or labelled for assistive tech. Made keyboard-operable and aria-labelled, and switched to a real opt-in, unchecked by default, with Accept gated on it. | #11 |
| Assistive-tech semantics (WCAG 1.1.1, 4.1.2) | ||
| header logo | Link had no accessible name; the logo image was announced as content. Gave the link a name, marked the image decorative. | #17 |
| perks & redeem | No page <h1> anywhere, and card titles competing with section titles. Rebuilt the heading hierarchy: page h1, section h2, card h3. | mobile |
| truncated labels | Category tiles, gift-card names and offer titles clipped with no way to read the full string. Added [title] full text. | #14 |
| share dialog | Clear control announced the literal word “close” as its label; replaced with a proper icon control. Share disabled until a colleague is selected. | #12 |
| Contrast and colour | ||
| brand cards | White count text sat on a gradient, which contrast tools cannot evaluate and which varied by tenant. Swapped to a solid brand fill holding a consistent, machine-readable ratio. | #6 |
| focus ring | Six different browser-default focus artifacts across dropdowns, chips and inputs. Replaced with one --focus-ring token at 2px; halo opacity 0.25 → 0.10. | canon |
| Touch and pointer | ||
| hamburger | Tap target below the comfortable minimum on phones. Raised to 48px. | mobile |
| department filter | Filter chips under-sized for touch on tablet. Re-spaced to real targets. | tablet |
| offer tiles | Filled “Get Offer” hover state stuck on after tap and after dialog close, because it was written for a mouse. Scoped to hover-capable pointers. | #13 |
| colleague picker | Card-level hover lift implied the whole card was clickable when only the “+” was. Scoped the feedback to the element that actually responds. | canon |
| Structure and layout | ||
| redeem grids | A dead Bootstrap 3 class meant gift-card tiles never stacked full-width on phones. Corrected across redeem, WFH and perks tile grids. | mobile |
| smart banner | Overflowed the viewport horizontally on phones. Constrained and re-scaled. | mobile |
| bottom nav | Sat under the home indicator on notched devices. Added the safe-area inset. | mobile |
| empty carousels | Recommended-offers rendered an empty shell when the array was empty. Module now hides. | mobile |
Two of these deserve their own note
The consent checkbox was not really an accessibility fix. It was reachable-by-keyboard
work that turned into something else: the box had been pre-checked by default and the Accept
action did not depend on it. Making it keyboard-operable and correctly labelled meant admitting
that the control had been decorative. It is now unchecked by default, and Accept is gated on
it. An audit that only looks for missing aria attributes never finds that.
Removing a focus ring is usually a defect. Several commits here strip focus outlines,
which deserves an explanation rather than a footnote. What was stripped were browser-default
artifacts (blue bracket marks inside dropdown panels, focus rails on category chips),
six inconsistent presentations of the same state. What replaced them is a single
--focus-ring token applied everywhere, plus a visible ring added to controls that
had none. Focus indication went up, not down; it just stopped being six different accidents.
06Mobile made it structural
The codebase was never built for phones. Client demand changed that, and making the app work at 400px turned a set of polish items into architecture.
Some of it was ordinary responsive work: a banner overflowing the viewport, a dead Bootstrap 3 grid class that stopped gift-card tiles ever stacking full-width, a bottom navigation sitting under the home indicator on notched devices. But the interesting failures were the ones where a desktop assumption had been encoded as an accessibility decision.
The clearest was the offer tile. Its filled state was written as a :hover rule,
which on a touch device sticks after the tap and stays stuck after a dialog closes, so
the tile lies about its state. The fix is to scope hover feedback to hover-capable pointers,
which is a one-line media query and an admission that hover was never a state, it was a
mouse.
The same applied to heading structure. Perks and redeem had no page <h1> at
all, survivable when everyone is looking at a 1440px layout and navigating by eye,
disqualifying when someone is navigating by headings on a screen reader. Rebuilding the
hierarchy was not a mobile fix. Mobile is just where it became impossible to ignore.
07What this half is worth
The design-system work made the product consistent. None of it made the product usable, and the two are not the same claim.
A token file cannot tell you that the Nominate row has a click handler and no
tabindex. A colour ramp cannot tell you that white-on-gradient is untestable.
A component library cannot tell you that a pre-checked consent box is a design decision
somebody made. Consistency is a property of the code; usability is a property of the
encounter between the product and a person, and only one of those can be linted.
What the foundation did buy was speed. Every fix here landed behind the same guardrails, consumed the same tokens, and needed no new values; five of them touched no SCSS at all. Consistency made the usability work cheap enough to do exhaustively.
The honest limit: these ship in waves, and the numbers that would settle it (the end-user rating, task completion on a phone) come after the sequence completes. What I can show is the gap I found, the method I used, and the fixes that are live. The rest is a measurement I have not taken yet.