The redesigned expenses list with its summary bar and table

A page migration that left the legacy behind

InvO

People had learned our expenses list, and still clicked where it doesn’t work. How to pay down design debt on a page migration without breaking what they already know.
Outcome

Table, summary bar and column settings handed off to dev. Not in production yet.

Outcome

Table, summary bar and column settings handed off to dev. Not in production yet.

Scope

Clarity analysis, design system components, screens, developer spec

Scope

Clarity analysis, design system components, screens, developer spec

Role

In-house UX designer

Role

In-house UX designer

Timeline

September 2026

Timeline

September 2026

No headings found on page
No headings found on page

People had learned our expenses list. They still clicked where it doesn’t work.

That is what the session recordings showed me and a number on its own wouldn’t have. Even through a pattern they had learned, people kept falling back into the bigger pattern — the one every other table on the internet honours, and ours didn’t.

FakturaOnline is moving its front end from Vue to React, page by page, onto a new design system, and Výdaje was one of the first pages to go. Which makes the whole thing one hard question: how do you pay down design debt without breaking what people have already learned?

From: the production table with eight columns, headers wrapping onto three lines and a sideways scrollbar. To: the new table with six columns and a status column.

What Clarity showed. On the expenses section, 24.5% of clicks were dead, twice the invoice list’s rate. The hot spot was the file upload on the expense form. On the invoice list, which uses the same table, around 42,600 clicks a month landed on rows that did nothing.


Where we broke the global pattern

Three places, one defect.

The row. Production makes only the number clickable. That is where the 42,600 goes. Nobody there is confused — they are doing what every other table taught them.

The phone. The row opened from a 30 px arrow and nowhere else. Same defect, smaller target.

The layout. Production picks the mobile layout by sniffing the user agent, so a desktop browser dragged narrow gets the desktop table and a sideways scrollbar nobody designed.

All three now do the ordinary thing. The whole row opens the record, the whole row is the tap target, and the layout follows width at the same breakpoint as the rest of the system. The row click stays a convenience on top of a real link, because a row is not a link and keyboard users would lose exactly what the mouse users just got.

Even through a pattern they have learned, people fall back into the bigger one. If you never built that one, it just looks like they can’t click.


And where we were only sloppy

The rest isn’t a broken pattern, it is accumulation. Nobody had ever defended any of it.

Change

Before

After

Sort arrow

Could point the wrong way

Always matches the order

Default columns

Eight, half of them for the accountant

Six, with status; the rest in settings

Column settings

No way back to the default

An “Obnovit výchozí” (restore default) link

Empty cells

Blank, as if it failed to render

A light grey dash

The old default had eight columns and neither of the two I’d call load-bearing: no total with VAT, no status. That set was never decided; it accumulated. The new one is what fits the container without a sideways scrollbar, and the four that came off are accountant work — the VAT date, the net amount, two tax flags.

Trade-off. The accountant now fetches those four columns every session so that the sole trader who never opens them stops scrolling sideways. I took the deal because the accountant knows what she is looking for and the sole trader does not.

The column settings list opened over the table, with the Obnovit výchozí link at the bottom.

Which is why column settings also got an “Obnovit výchozí” link, and why locked columns stay in the list as disabled, checked rows instead of vanishing from it.


Same widget, honest numbers

The summary bar above the list keeps its shape: a total with a donut and one tile per status. What changed is how it behaves.

Change

Before

After

Status tile

Only the 16 px checkbox is clickable

The whole tile toggles

Total and donut

The whole period, ignoring filters

Only the statuses left on

Tile with zero

Can’t be switched off

Works like any other tile

Donut tooltip

The chart library’s default

The design system’s tooltip, named like the tile

The total was the one number on the bar that lied about the table right under it.

Making the total follow the filters is obvious right up to the moment you try the same thing on the tiles — switch Zaplaceno off and five of the six read 0 Kč, which removes the only thing you were choosing between. So the total and the donut describe what is in the table, and each tile keeps describing its own status whether it is switched on or not.

The summary bar: a total card with a donut and status tiles.


The filter I didn’t build

Same rule, pointing the other way. Faceted filters are where our lists are heading and I had already built the chip and a working faceted row. Then I checked the filters we have: on the invoice list, 100,000 views produced zero dead clicks on the status tiles.

Nobody is falling back to anything there. People have learned that control and it works for them, so it is not debt — it is just not what I would design today. Our core user issues one or two invoices a month and doesn’t want to relearn a screen.

Trade-off. The lists keep a filter model I have already outgrown, and finished work sits unused in the file. What it buys is that nobody relearns the one control they touch every time.

The hard part wasn’t the decision. It was looking at a finished component I can’t use.

For the record, the thing that actually bothers me on this screen is the filter zone, and that the buttons aren’t in one place — there is one sitting right next to the heading. That is not this stage’s job, so it isn’t in this ticket.


Handoff

Every rule above lives in the design system repo twice: a .md spec written for a person, and a .manifest.json the repo validates. A developer builds from those two files without opening Figma, and CI checks the prose and the machine-readable facts still say the same thing. The spec also records what production does today, next to what the component does instead — a rule that only exists in a Figma frame gets read as a suggestion.

And the gaps are written down as gaps. This pass has no loading state, no first-use empty state, no “couldn’t load the list” frame. I’d rather hand over a spec that says a state is missing than one that invents it, because an invented state ships.


What it changed, and what I don’t know

Every table I build now comes out of one component, so screens come together faster (and all my tables are pretty now). There are no after-numbers, because the page isn’t in production. That is not a small asterisk.

What I’d do differently: go to the recordings earlier, and with the question already written down. The aggregate numbers told me where to look and nothing else.

Dead clicks find the spot and can’t tell you a thing about why. I knew that. Did the fast thing anyway.

Until it ships, this is a very well-documented hypothesis.


Also I did facelift for landings, but its a whole new story… 👀

The redesigned expenses list with its summary bar and table

What will we build?

Open to full-time roles and freelance projects

What will we build?

Open to full-time roles and freelance projects

© Ekaterina Pykhova, 2026

#footer —

© Ekaterina Pykhova, 2026

#footer —

© Ekaterina Pykhova, 2026

#footer —

© Ekaterina Pykhova, 2026

#footer —