← Back to work
Case 03 · Enterprise systems

A table that has to answer questions, not list records.

Redesigning the user management view for a directory sync platform, where tens of thousands of people flow from source systems into downstream directories and this table is the only place an administrator can find out what went wrong.

Work shown with enterprise-identifying details removed. Product name and interface are my own; original assets are not shown.

Role
UX/UI Designer
Users
District IT administrators
Tools
Figma, Claude prototyping, React
Status
Shipped to production
The context

One person, many identities.

A directory sync platform takes people out of a district's source systems and pushes them into everything downstream: directory groups, workspace tenants, and smaller systems like lunch programs, library catalogs, and bus routing.

A single person is rarely simple. They can arrive from more than one source feed, land in several destinations at once, and carry fields an administrator edited by hand after import. That last part matters more than it sounds, because a manually edited field no longer matches its source and will not be corrected by a resync.

The problem

The table was the debugging tool, and it could not debug.

When a teacher cannot log in, when a student is missing from a roster, when someone still holds access they should have lost, the administrator opens this table. It is where every question about a user gets answered.

A conventional user table shows name and status and hides everything that actually explains a problem.

What the table could not surface
  • Which source feed or feeds a person arrived from, when many arrive from more than one
  • Which downstream systems they actually reached, and which they did not
  • Which fields had been manually overwritten after import, and therefore no longer track the source
  • Any attribute outside the fixed column set, even though districts store different attributes and care about different ones
The prior view is described from memory rather than shown. The original interface is not reproduced here.
The real design problem

There is no column set that works for everyone.

Districts run different source systems, store different attributes, and care about different things. One administrator is chasing employee IDs, another needs distinguished names for a directory migration, another only ever looks at email.

The instinct is to add every field as a column. That produces a table nobody can read horizontally. The opposite instinct, picking a canonical set, produces a table that fails a different administrator every day.

The table had to be shaped by the person using it, without letting them shape it into something unusable.
The design

Four moves, each solving one failure.

One
Columns the administrator chooses, with a hard cap
A catalog of additional attributes can be added as columns, but only two at a time. Adding a third replaces the oldest rather than throwing an error. The cap is what keeps the table readable, and the silent replacement is what keeps the cap from feeling like a wall.
EmailEmployee IDdistinguishedNameDepartmentBadge numberCourse ID list
Two
Filtering that happens in the header
Turning on column filters converts every header into its own filter control, including any custom column just added. The filter lives where the data is rather than in a separate panel, so narrowing a set is a direct manipulation of the thing being narrowed.
Three
Manual overwrites made visible
A dedicated column surfaces which fields were edited by hand after import, and what they were set to. This is the state behind the most confusing support cases, because the record looks correct in the source and wrong downstream, and nothing in the old view revealed it.
Four
Actions bound to the filter, not the page
Export, unlink, and delete all operate on the entire filtered set rather than the visible page. That is powerful and dangerous in equal measure, so the count travels with the action.
The problem
  • Districts store different attributes, so no fixed column set fits all of them
  • The questions that matter are per-person: which feed, which destinations, what changed
  • Fields edited by hand after import stop tracking their source, invisibly
  • An action on a filtered set can reach far past the rows on screen
The design
  • Custom columns chosen from a catalogue, capped at two, oldest replaced
  • Every header doubles as its own filter control
  • A dedicated column surfacing fields edited by hand after import
  • Actions bound to the filtered set, with the exact count named in the confirmation
Design principle at work
A constraint stated once beats an error shown repeatedly. The two-column cap communicates itself by replacing the oldest column, so the limit is learned by using the tool rather than by being stopped by it.
The user table with two custom columns added and every header turned into a filter control

Column filters turn each header into a control, including columns the administrator added moments earlier.

The dangerous part

Delete filtered users is the riskiest control on the page.

A filtered-set action is exactly as sharp as the filter behind it. An administrator who believes they are deleting the ten rows in front of them, when the filter actually holds four thousand, has made an unrecoverable mistake in one click.

So the confirmation does not just ask whether they are sure. It states the real number, and separates it explicitly from what is visible.

What the confirmation has to establish
  • The exact count in the filter, not the count on screen
  • That those two numbers are different
  • That the action cannot be undone
Design principle at work
When an action operates on something the user cannot see, the confirmation is not a formality. It is the only place the scope of the action becomes visible at all.
Try it

The table, rebuilt and interactive.

I rebuilt this as a standalone prototype with fictional records, embedded below. Add columns until you hit the cap and watch the oldest drop out, turn on column filters, then open the delete confirmation to see how the filtered count is stated.

Sample data is fictional. Interface rebuilt from scratch in my own design system. Open full screen ↗
The decision

Why two columns, and not five?

The cap is a real constraint with real costs. An administrator comparing three attributes at once has to swap one out. A larger allowance would have handled that case, at the price of horizontal scrolling that makes the core columns harder to scan.

Two won because the table’s job is answering a question, not holding data. Once a row stops fitting on screen, the administrator is scrolling sideways to reconstruct a single person, which is the exact failure the redesign existed to fix. Two is the most I could add without reintroducing it.

Impact

Shipped, and in daily use.

The redesigned view is in production. Administrators can now answer the questions the table exists to answer: where a person came from, where they went, and whether someone changed them by hand along the way.

What I learned

Density is a reading problem.

Lesson 01
Configurability needs a ceiling to stay useful.
Letting people add every column they might want produces a table nobody can read. The cap is not a limitation on the feature, it is the feature.
Lesson 02
Put the filter where the data is.
Filtering from the column header keeps the question and the answer in the same place, instead of asking someone to hold a mental model of a separate panel.
Lesson 03
Surface the state that causes support tickets.
Manual overwrites were invisible, and were the reason records looked right upstream and wrong downstream. One column resolved a whole category of confusion.
Lesson 04
Scope is the thing a confirmation must communicate.
For an action that reaches past the visible page, the count is the warning. Asking whether someone is sure, without telling them how many, is not a safeguard.

The redesign did not add information to the table. It let each administrator decide which information the table was about.

Thanks for reading.