Practical web UI/UX / personal lessonsLesson 11 of 15
Practical web UI/UX · Lesson 11

Design search, filters, and useful empty states

A data view must explain what is included, why nothing is visible, and how a person can recover.

By the end: distinguish an empty collection from zero filtered matches and preserve exact record identity.

Choose the fields needed for the decision

An organizer looking for a waitlisted guest needs a name, session, and state. A financial audit would need different fields. A table is useful when rows share comparable attributes; a card can accommodate richer descriptions but may make scanning columns harder.

Keep exact labels and units where they matter. Sort must use the underlying value, not arbitrary visual position. Selection should refer to stable record identity, not the current row number, because filtering or sorting changes that number.

Read a row as a decision

Suppose an organizer needs to identify the guest waiting for Clay basics. Reading every booking aloud would eventually work, but the interface can keep the relevant comparisons together. Each row represents one booking, not one person or one seat. Jo Park's row contains three seats within a single waitlisted booking. That distinction matters before counting results. A message saying one booking is shown does not say that only one guest place is requested or that any place has been confirmed.

Start with the visible reference and find Jo Park without using the search controls. Read the session and state beside the name. Mina Hart also has Clay basics, but her booking is confirmed. Alex Reed is waitlisted, but for Ink printing. These are intermediate candidates that fail different parts of the task. Keeping session and state visible lets the organizer reject them without opening several detail views. A row containing only a guest name would require extra inspection before the same decision.

Three candidates, one matching booking

BookingSessionStateFits the task?
Mina Hart · b1Clay basicsConfirmedNo: wrong state
Alex Reed · b2Ink printingWaitlistNo: wrong session
Jo Park · b3Clay basicsWaitlistYes: both conditions
Session alone would retain Mina and Jo. State alone would retain Alex and Jo. Requiring both conditions leaves Jo, whose one booking requests three seats.

The repeated structure is what makes a table useful here. The Booking column contains identity plus session, day, and seat count. The State column keeps confirmation status in a comparable position. A card could hold these facts too, but a long description per guest would interrupt the repeated scan. Conversely, a workshop catalogue with different descriptions and equipment requirements might need richer cards. Choose the structure according to the comparisons people must make, rather than treating tables as inherently more professional or cards as inherently easier.

Read the state as text, even if a future treatment also gives it a color. Confirmed and Waitlist describe different booking conditions. A muted or amber mark alone would require the organizer to remember a color key and perceive the distinction reliably. The same principle applies to headers. A visual alignment can suggest which column a value belongs to, while actual table headers preserve that relationship for software. Styling the first line in bold does not create a header association by itself.

There is also information this table does not supply. Day 12 is a constructed session-day value, not a full date with a month, year, or timezone. Three requested seats do not establish current workshop capacity. A waitlist row does not say whether the guest has been contacted. If the task changed to sending invitations, those missing facts would need an explicit source and appropriate controls. Do not infer an available operational action merely because the row looks like a staff dashboard.

A booking row and its complete local detail

Static row excerpt

Jo Park

Clay basics
Day 12, 3 seats

State: Waitlist

Static detail excerpt

Booking b3: Jo Park

Clay basics, day 12.
3 seats. State: Waitlist.

Detail action label: Close booking detail

The row supports comparison; the detail repeats the exact identity and adds the visible booking identifier. This static example does not offer an invitation or confirmation action.

Now describe the target before touching a control: a waitlisted booking whose session is Clay basics. Predict how many rows should survive using the reference. Only Jo Park satisfies both conditions, so the expected answer is one booking, with stable identifier b3 when opened. This prediction gives you a concrete way to inspect the working interface. If a revised layout made Mina's confirmed booking seem eligible, its increased visual neatness would not count as successful support for this task.

Try the interface

Your task: Find the Clay waitlist booking, open it, and clear the filters. Then switch to an empty account and compare the recovery offered.

Query real demo rows

Studio Desk demonstration. Actions affect this page only, not a real booking, account, or payment.

Original six-record reference

BookingState
Mina HartClay basics
Day 12, 2 seats
Confirmed
Alex ReedInk printing
Day 10, 1 seat
Waitlist
Jo ParkClay basics
Day 12, 3 seats
Waitlist
Sam OrtizInk printing
Day 10, 1 seat
Confirmed
Noor AliClay basics
Day 12, 1 seat
Confirmed
Lee ChenInk printing
Day 10, 2 seats
Waitlist

Searchable booking list

1 of 6 account bookings shown.

BookingState
Clay basics
Day 12, 3 seats
Waitlist

The fixed reference contains six fictional records. The selected account can be empty or populated. Presets replace query, filter, and sort; individual controls change only their setting. Criteria or dataset changes close detail. Clear filters does not populate an empty account. No pagination, server query, or permission model is implied.

Follow the query through the records

Use the Clay waitlist preset in the working model, then read all three selected criteria. The query is clay, the state is Waitlist, and the order is Guest name. A preset replaces that whole combination. It does not add another condition to the current combination. The selected list should show Jo Park alone, while the six-record reference stays unchanged. Open Jo's booking and compare the displayed identifier, session, day, seat count, and state with the reference facts before changing anything else.

Search here means a case-insensitive substring in the guest name and session title joined with a space. Leading and trailing spaces in the query are ignored for matching. Change the query to CLAY with spaces around it and keep Waitlist selected. Jo remains the result because case and outside spaces do not change this match. The input can still display the spaces you typed. Normalizing the matching rule is different from silently rewriting the visible text, so judge the result rather than expecting the input to change appearance.

Clay first, then the state condition

Account before criteria

6 bookings: b1, b2, b3, b4, b5, b6

Name or session contains clay

3 bookings: Mina b1, Jo b3, Noor b5

Also requires state Waitlist

1 booking: Jo b3
This is a worked query trace, not an extra filter control. Search narrows six records to three; AND with Waitlist narrows those three to one. Changing sort would only reorder survivors.

The query and state condition combine with AND. A record must pass both tests. Change Booking state to All while leaving clay in the search box. Jo, Mina, and Noor now qualify because their session matches and no state is excluded. Search does not include the displayed word Confirmed, the seat count, or the stable identifier. Try b3 with state All. Zero results are expected even though booking b3 exists. The control promises a name or session search, not a search of every visible or hidden attribute.

A substring is any consecutive piece of the searched text, not necessarily a whole word. With state All, the query in matches Ink printing bookings. It also matches Mina Hart because mina contains those adjacent letters. Four bookings therefore survive this query. This is a useful failure case for a person's initial expectation that in means only Ink printing. The mechanism is consistent, but the expectation may differ. A production service might need another search rule; this small model does not offer exact-word or fuzzy matching switches.

The letters in do not mean only Ink printing

Static result excerpt · query in · state All

4 of 6 account bookings shown.

GuestText that matches
Alex Reed · b2Ink printing
Lee Chen · b6Ink printing
Mina Hart · b1Mina Hart
Sam Ortiz · b4Ink printing
The case-insensitive substring rule also finds in inside Mina. These four rows are expected under query in and state All; the model does not interpret the query as a session category.

Notice what happens to the detail panel when you edit any criterion. It closes, even if the previously opened booking would still match. This is an explicit policy that avoids showing an unexplained detail beside a different result set. Reopen Jo after returning to Clay waitlist. Closing the detail with its own Close booking detail action returns focus to Jo's row action. Changing a query is a different event from closing detail directly, so do not assume both events make the same focus move.

Finish this exploration by changing only the sort control. The query and state should remain as they were. Then choose All bookings by day and inspect how a preset differs from that single-control edit. The preset clears the query, removes the state restriction, and changes the order together. Clear filters uses that same all-bookings, day-order combination. It is a named recovery action, not a command to restore every aspect of the account or recreate records that are currently absent.

Make query state inspectable

This lab searches guest names and session titles using a simple case-insensitive substring. A state filter combines with that query, and a sort orders the surviving records. The result count names the visible subset; it does not imply that a server or an entire large account was searched.

The original six-record reference stays visible. Presets replace the selected query, state filter, and sort together. Editing one control changes only that control. Opening a result shows a local detail panel; changing the criteria closes it so the interface does not retain an unexplained hidden selection.

Separate ordering from identity

Filtering answers which bookings are included. Sorting answers in which sequence those survivors appear. Neither operation changes the booking itself. With All bookings by day selected, the earlier session-day value 10 comes before 12. Within day 10, the stable identifiers order Alex b2 before Sam b4 before Lee b6. The day-12 group then contains Mina b1, Jo b3, and Noor b5. This secondary comparison makes equal-day results predictable rather than leaving their order dependent on an unexplained shuffle.

Guest name order uses the full displayed name string in ascending string comparison. For these six names, the order is Alex, Jo, Lee, Mina, Noor, Sam. It is not surname order, so Noor Ali does not lead because Ali precedes Chen. Nor does this simple comparison claim to implement culturally appropriate ordering for every writing system. The current names are distinct, so no equal-name example appears. The name comparison returns equality for identical names rather than applying the day sort's explicit identifier tie rule.

The same records in two supported orders

Session day order

  1. Alex · b2 · day 10
  2. Sam · b4 · day 10
  3. Lee · b6 · day 10
  4. Mina · b1 · day 12
  5. Jo · b3 · day 12
  6. Noor · b5 · day 12

Guest name order

  1. Alex Reed · b2
  2. Jo Park · b3
  3. Lee Chen · b6
  4. Mina Hart · b1
  5. Noor Ali · b5
  6. Sam Ortiz · b4
Day order uses ascending day then identifier. Name order compares the full guest-name strings. The second position changes from Sam to Jo even though the records themselves have not changed.

Use the working model to verify both orders with the query blank and state All. Before switching, locate the third visible booking. In day order it is Lee Chen, identifier b6. In name order it is Lee again, which happens to conceal the danger of remembering positions. Compare the first row instead: Alex remains first too. Now inspect the second row, which changes from Sam to Jo. A few unchanged positions do not establish that position is a safe way to identify an object.

The identifier stays attached to the booking while its position changes. Jo is b3 when found by Clay waitlist and remains b3 in an unfiltered view. Row number two is only a statement about the current presentation. Imagine a list that remembers the second row as selected, then sorts without updating its selection policy. The highlight could move to another guest while a detail panel still describes Jo. This mismatch can direct an organizer's attention to the wrong person even when every individual row contains correct text.

A position changes without changing the booking

ViewJo’s positionJo’s identityDetail policy
Clay waitlist1b3Can open b3
All by day5b3Criteria change closes detail
All by name2b3Criteria change closes detail
The lab closes detail on any criteria change. It does not carry a position-based selection into the new list. Reopening Jo retrieves b3, regardless of the row number.

This lab avoids that mismatch by closing detail when criteria change. Another application might preserve selection by identifier and explicitly explain that the selected booking is outside the current results. Both can be coherent contracts, but they support different tasks. An organizer comparing a changing shortlist may prefer a clear selection reset. Someone editing a long record may need the detail retained. The decision concerns continuity of work, not whether keeping a panel open looks smoother during a demonstration.

Counts must follow the same distinction. One of six account bookings shown describes the filtered subset, not a missing five bookings or a total of six seats. Sorting should not alter that count because it only rearranges survivors. Try a query with one result and switch the sort. If the visible count changed, you would need an explanation beyond ordering. In larger products, counts may refer to loaded pages or authorized records, but those additional meanings are absent from this complete six-record local example.

Give different empty states different repairs

An account with no bookings needs a route to create or import an appropriate first record. A populated account with no matching results needs a way to change or clear the query. A loading state and a failed request are different again; an empty-looking region should not silently stand for all four.

In the demo, Clear filters cannot populate an empty account. Restoring sample records is an explicitly labeled teaching action. In a large service, pagination, authorization, and server filtering require additional state and count semantics; this six-record model does not implement them.

Make absence explain its own repair

Choose No matches: glass while the sample account is populated. The selected count becomes zero of six, and the empty region says No matching bookings. These statements fit together. Records exist, but none passes the current query. The search box remains visible so you can inspect the cause. A generic blank panel would leave several possibilities unresolved: an empty account, a failed load, or a restrictive query. A useful empty state identifies the condition before asking the person to perform a repair.

Try the smallest repair first. Replace glass with ink without changing the state filter. Three bookings appear because the preset had state All. If you instead keep glass and choose Confirmed, there are still zero matches. A state change cannot make an absent session substring appear. This gives recovery controls different meanings. Revising a query changes one condition; Clear filters removes the query and restriction together and returns day order. Neither operation promises that a particular guest or session will exist in the account.

Two empty regions with different causes

Static populated-account state

Search: glass
Booking state: All

0 of 6 account bookings shown.

No matching bookings

Change the current query or filters.

Recovery action label: Clear filters

Static empty-account state

Search: glass
Booking state: All

0 of 0 account bookings shown.

No bookings in this demo account

Clearing filters cannot create records.

Teaching action label: Restore sample records

Zero of six points to restrictive criteria. Zero of zero points to an empty current account. Clearing criteria repairs the first case but cannot create data in the second.

Now choose Empty account while a restrictive query is still visible. The count is zero of zero, and the message names the empty account rather than blaming the query alone. Press Clear filters. It changes the criteria but cannot produce bookings because there are no current records to inspect. Restore sample records is a separate teaching action that repopulates the account and resets the criteria to All bookings by day. This deliberate distinction prevents a recovery label from promising an effect it cannot cause.

The count has a status role in the working example, so its changed text can be made available without forcing focus away from the search field. The rows themselves are not a substitute for that short status. Conversely, announcing a count is not the same as explaining which booking is correct. After editing, the organizer still needs readable rows and headers to interpret the result. Actual announcement timing and usefulness require assistive-technology testing; the existence of a role alone does not establish that every user hears an ideal message.

Clear criteria and restore data are different actions

Local state or actionCriteria afterwardVisible count
Empty account with glassglass · All · name0 of 0
Clear filtersblank · All · day0 of 0
Restore sample recordsblank · All · day6 of 6
The query can change while the dataset remains empty. Only the explicitly labeled sample restoration repopulates this local account; neither action calls a server or persists anything.

For a transfer task, imagine the organizer knows only that a Clay guest requested three seats and is waiting. In the existing model, search Clay, restrict to Waitlist, and inspect the surviving row's seat count. You cannot filter by seats directly because there is no such control. Compare that supported path with a proposed interface that hides seats until detail. The latter can still complete the task, but it requires opening the booking to verify the last condition. Count successful identification and errors, not merely the number of clicks.

Ask someone else to identify that booking without naming the controls or the expected guest. Observe whether they understand the result count, find the state restriction, and recognize the detail. Do not call your own successful walkthrough a usability study. The local model establishes exact query outcomes and recovery behavior for fictional records. It does not test a server, permissions, saved filters, or persistence across reloads, and it supplies no evidence that the selected presentation improves every organizer's work.

Exercises

Repair a misleading empty state

The interface says "No bookings yet" after the user searches for glass in an account containing six other bookings. What should it say and offer?

Hint

The collection is not empty; the visible result set is.

Worked answer
ConditionUseful recovery
No records in accountCreate an appropriate first record
No filtered matchesChange or clear filters
Request failedRetry or inspect the failure
The empty region needs an explanation.

Say that no bookings match the current search or filters and offer to clear or revise them. Preserve the query so the person can see why the state occurred. Do not invite them to create their first booking as if their existing records were gone.

Keep a selected record stable

The third visible row is selected, then the list is sorted. Why is storing only row number 3 unsafe?

Hint

Row positions are not identities.

Worked answer
Stored valueMeaning after sorting
Row number 3Possibly a different booking
Stable id b3The same booking
Closed detail by policyNo hidden current selection
Selection behavior is part of the interface contract.

A different record may now occupy that position. Store a stable booking identifier and decide explicitly whether the selection persists when it becomes hidden. This lab instead closes detail on criteria changes, avoiding an unexplained retained selection.

Sources and further reading

Selected guidance and technical definitions were reviewed, not a complete usability literature or accessibility audit. The interfaces are original teaching examples. They do not establish measured improvements in task performance. These optional links are not runtime dependencies.