All articles

You Don't Need to Be a Genius to Build an Accessible UI

A lot of teams now ship without a designer, and dark UIs show it first. How I reworked a car marketplace's filters with Claude, the prompts I used, and what you still have to test yourself.

A bird's-eye view of a car filter panel on a dark background, with every filter in one place: price, seats, year, kilometres, new or used, body style cards with car icons, drivetrain, colour swatches, fuel, make and model, and transmission. A blue button at the top reads Show 6 cars.

I’ve been doing this for almost 12 years, so the moment I log in to a product, I notice things. Is this clickable? What should I do next? Maybe it looks good on the iMac it was built on, but people also use Windows laptops, phones and tablets. Can they even read it?

A lot of developers now build without designers. They use their own style, it looks fine in their dark theme, and it ships. Accessibility is usually the first thing to go, because the person who built it can’t see the problem.

Here’s the thing: it’s not hard anymore. You don’t need to be a freaking genius. You can just do it.

To show you, I’ll use a product for researching and buying cars. I went through their live product, rebuilt the filters with Claude and sent them the proposal. I don’t know how their team works, and this isn’t a dig at them, so I’m leaving the name out. Their filters are just the example I know best. Every number below comes from their live site and my rework, tested on November 4, 2026.

The code isn’t the problem

I expected the code to be the issue. It isn’t. Their price slider has labels, the arrow keys move it, a focus ring shows up, and Enter and Escape work on the controls around it. Whoever built this chose solid components.

The problem is everything on top.

Live product

The live price filter. Muted blue and gray bars above a range label, and two white dots on a track that barely shows against the dark panel.

My rework

My rework of the price filter. Bright blue bars for the selected range, gray bars outside it, a blue range between two handles, $0 and $100k+ at the ends, and fields holding $20,000 and $45,000.
The same filter, both set to $20,000 to $45,000.
  • The track is almost invisible. The selected range is 1.04:1 against the panel, and the track’s edge is 1.12:1. What you really see is two white dots.
  • The label is too quiet. Before you touch the slider, it says “Any” at 12 pixels and 3.93:1. Small text needs 4.5:1.
  • The bars look clickable, but they aren’t. Clicking one does nothing, and hovering shows nothing. Clicking the track does work, if you can find the track.

In my version, the selected bars and range are 7.35:1. Hover a bar and you see its price. Click it and the closest handle moves there. Or just type the price. For a screen reader, the handle exposes “$22,500” instead of a bare number.

The prompt wasn’t clever. This is what I actually typed:

What I typed

I want you to allow hovering on this bar here and also show a small, compact tooltip with the price. If I click on it, it moves to that one, so I can grab it and move it, or just click on different things and it moves. The closest one, either the left one or the right one, moves there.

This is what came out of it:

Hover a bar for its price, click it to move the closest handle, or grab a handle and drag it. The fields follow.

Dark UIs fail first

On a dark UI, all your grays sit close together. A border two shades lighter than the panel looks deliberate on a good screen. On a cheap laptop, it’s gone. Here’s what I’d check first.

Anything you can click needs 3:1. Their unticked checkboxes are 1.12:1 against the panel, so you can barely find them. Mine use a gray that reaches 3.48:1.

Live product

The live Make and model list: Acura, Airstream, Alfa Romeo and more, with checkboxes that almost disappear into the panel.

My rework

My rework of the list: a Search makes field, then Toyota, Ford, Honda and more, with clearly outlined checkboxes.
Same filter, same dark theme. Only one of these checkboxes looks like something you can tick.

Don’t make one accent do everything. Their Sign up button puts pale text on light blue, at 2.06:1. In my theme, the button blue carries its label at 5.23:1, but as text on the dark panel it drops to 2.9:1. So buttons get that blue, and text gets a lighter one at 7.35:1.

Put the rule in the token, not the screen. This is where a design system earns its keep. The contrast decision lives in the theme, so the next filter someone adds starts out right:

// 300 is the edge of every field and control:
// 3:1 against the page and the cards.
300: '#737373',

// The button blue is too dark to read as text
// or an icon on this page, so text takes lighter stops.
const readableBlue = { 400: '#8FAAFF' /* ... */ };

Don’t flip a light theme and call it done. I re-themed a library made for a light UI by flipping its gray scale. Tooltip text vanished because the old dark gray had become pale. A checkbox kept its hard-coded white box. The phone field kept its light borders. After a flip, test every component, not just the page you’re looking at.

Let people zoom. Their viewport tag includes maximum-scale=1, user-scalable=no, which asks phones to block pinch zoom. If you need bigger text, that’s a problem. Delete it. It’s one line.

No dead ends

My main philosophy has always been: do not make me think. Don’t make people work out what’s clickable or what to do next. You get there by designing for all the states, and the one everybody forgets is the one where nothing comes back.

Live product

The live no-results screen: two filter chips, 0 vehicles, the line No vehicles match your current filters, and a Clear all filters link.

My rework

My rework's no-results screen: No cars match all of these, with Remove Over $95k showing 16 cars, Save search, Ask AI, and three closest matches labelled with what each one misses, such as Chevrolet, not Kia.
Zero results on both. Only one of them gives you somewhere to go.

On the live product, you can still remove one chip at the top, but the only action on offer wipes everything you set up.

Mine leads with the one change that brings back the most useful number of cars: Remove Over $95k, 16 cars. You can save the search and get an email when a match shows up, or ask the AI about it. Underneath are the closest cars and what each one misses: “Chevrolet, not Kia”, “$25,705 under your minimum”.

The prompt was one line:

What I typed

we need to design for some empty states where there is no results but make them actionable

The same pass covered the other dead ends. A search it can’t read says so, and a typo gets “Did you mean”. It depends on the use case, of course, but a dead end should lead to something else.

What I’d paste into Claude

If you don’t have a designer, start every project with this. The principle comes from Steve Krug’s book, and it’s worth reading.

Paste this first

Always follow the principle of “Don't Make Me Think” on what's clickable and what I should do next. Keep it human, warm and straight to the point. Follow the accessibility and WCAG rules.

Claude basically knows the rest. For something you’ve already built, follow it with an audit:

Then this

Audit this screen. Measure the rendered contrast against the real backgrounds: 4.5:1 for small text, 3:1 for controls and their edges. Check every state: hover, press, focus, disabled, loading, empty and error. Check the keyboard: Tab, Enter, Escape and the arrow keys. Check it on a phone. Tell me what fails, with the numbers. Then fix it in the tokens and shared components, not screen by screen.

And one rule of mine that’s worth stealing:

What is clickable gets a hover and a press. What is not gets neither.

Then go click around

AI will still fall short on many things. That’s where you come in. You have to know what to tweak, and you only find out by using it.

My first rework still needed a contrast pass. The hover on tertiary buttons was barely visible. On phones, hidden menus and filter rows were catching taps and keyboard focus. You don’t spot that by reading a diff. So after every round, I do the same thing:

  1. Check the contrast.
  2. Test it on different screen sizes, then specifically on mobile.
  3. Open it and click around. Does it work?
  4. Put the mouse down. Does it work with the keyboard? Does it work on Escape? What happens when I press Enter? Can I move with the arrow keys?

That’s how you end up with a search like this one, where the keyboard can do everything the mouse can. If you want more, I wrote about what living in Claude is really like.

The rework's search bar holding Under $40k and SUV chips. An open list suggests colours and years with a count for each, and its footer shows the keys: arrows to move, Enter to add, Backspace to remove the last. Beside it, the filter panel shows the same choices.
Type, move with the arrows, press Enter for a chip. Escape closes the list, and Backspace removes the last chip.

A clean scan doesn’t mean you’re done

Then run a scanner. I ran axe, a free accessibility checker, on both:

Check Live product My rework
axe failures, filter page 117 elements across 4 rules 0
axe failures, phone 1: pinch zoom blocked 0
axe failures, open search list No search list on this page 1: a button inside a listbox
Selected price range 1.04:1 7.35:1
Unselected track 1.12:1 (its edge) 1.86:1
Unticked checkbox edge 1.12:1 3.48:1
Slider value exposed 21000 $22,500
Click a histogram bar Nothing Closest handle moves
Type an exact price No Yes
Tab presses to reach price 228 149
Tested November 4, 2026 in Chrome with axe 4.10, WCAG A and AA rules, at 1440 and 390 pixels wide. Contrast is measured from rendered pixels against the panel behind each element.

Their biggest number is 114 filter rows that nest one interactive control inside another. The others are a dropdown with no accessible name, the Sign up button and the blocked zoom.

Now look at my column. The filter page scanned clean, and it still isn’t right:

  • My unselected track is 1.86:1. That’s below the 3:1 I just held their checkboxes to. Axe checks text contrast, not controls, which is also why it never flagged their 1.04:1 range.
  • It takes 149 Tab presses to reach my price filter, because every listing card comes first. Better than 228. Still awful.
  • My open search list has a real failure: a button inside a listbox. Axe was right about that one.

None of this is hard. The track can use the same gray as my field edges, which is 3.48:1. A skip link deals with the tabs. A few more prompts. And neither a scan nor a keyboard pass tells you how it sounds, so turn on VoiceOver or NVDA and listen.

You never get it right from the first shot.

Start with these five

  1. Remove user-scalable=no if it’s in your viewport tag.
  2. Put the three rules above in your project’s instructions.
  3. Run the audit prompt on the component your users touch the most.
  4. Put your mouse away for five minutes and try to use your own product.
  5. Find your worst dead end and ask Claude to make it actionable.

So do you still need a designer?

Not all the time. What a good designer does is set up the foundation. A checkbox isn’t something you have to keep changing. You design it once, with its states, and it’s done. Then components scale, components morph, filters appear, multiple filters appear, and you reuse everything.

That’s why this rework was fast. I didn’t start from zero. I started from the design system I built for TimiTomi, the booking software I’m building for studios, and themed it for their dark UI. Thirty of the 34 commits on its main branch landed in one day. Claude could move that fast because the buttons, inputs, tabs and empty states already existed.

You can take it a long way without a designer. You can get good enough for many, and for many, good enough is good. Keep it that way. That’s not a big issue. But at one point, you keep adding, you keep improving, and you need somebody in the loop with everything.

A design system is never a one-stop shop.

It goes out into the wild, and you have to make changes to it. Things adapt, things break, new error states appear. You keep polishing it for the next use case.

So no, you don’t need a genius. You need someone who keeps opening the product and pressing Tab.