1 min lesson
The problem bank
Work through the cases in "The problem bank", pairing each signal with the move that fits.
Step 1 of 2
The problem bankPick one per session
Each of these is a real interaction a Design Engineer ships and each hides a specific trap that separates a working answer from a polished one.
Input that filters a list, opens on type, commits on Enter.
Trap: keyboard. Arrow keys move a visual active option without moving DOM focus; aria-activedescendant ties them together.
Tests whether you know ARIA from memory, not from a docs tab.
Render 50k rows with only the visible window in the DOM.
Trap: the math. A spacer div holds total height; you compute the start index from scrollTop and over-render a few rows so fast scroll doesn't flash blank.
Tests performance instinct under a real DOM-cost constraint.
Stack of dismissable notifications with enter/exit motion and auto-dismiss.
Trap: timers. Pausing on hover, resuming on leave and cleaning up on unmount so a dismissed toast's timer never fires.
Tests state machine clarity and effect cleanup.
Tab-style selector with a sliding indicator that animates between options.
Trap: the indicator. You measure the active segment and translate the pill with a transform, not by animating width/left.
Tests motion that stays on the compositor at 60fps.
Learn more
Full explanation
Lead with the contract before you type the component
// Combobox: filter a list, keyboard-navigable, commits a value. // Props type ComboboxProps = { options: { id: string; label: string }[]; value: string | null; onChange: (id: string) => void; }; // Invariants to say aloud: // - activeIndex is the visually-highlighted row, NOT DOM focus // - input keeps focus the whole time; aria-activedescendant points at the row id // - Escape closes and restores the committed value; Enter commits activeIndex // - empty filtered list still renders a 'No results' row, not a void
Learn more
Advanced table
Score yourself like the interviewer will
Score yourself like the interviewer willRubric
- Dimension
- Correctness
- Pass bar
- It works for the happy path and the two cases you named up front
- What sinks it
- Demos once, then breaks on the second interaction
- Dimension
- Edge cases
- Pass bar
- Empty list, single item, rapid input, last-item arrow wrap
- What sinks it
- Crashes or no-ops on an empty filter; arrow falls off the end
- Dimension
- Code clarity
- Pass bar
- Named handlers, no nested ternary soup, a reader follows it cold
- What sinks it
- One 80-line component nobody could review in five minutes
- Dimension
- Polish
- Pass bar
- Focus ring, hover/active states, a transition that isn't janky
- What sinks it
- Functionally right, visually a wireframe
| Dimension | Pass bar | What sinks it |
|---|---|---|
| Correctness | It works for the happy path and the two cases you named up front | Demos once, then breaks on the second interaction |
| Edge cases | Empty list, single item, rapid input, last-item arrow wrap | Crashes or no-ops on an empty filter; arrow falls off the end |
| Code clarity | Named handlers, no nested ternary soup, a reader follows it cold | One 80-line component nobody could review in five minutes |
| Polish | Focus ring, hover/active states, a transition that isn't janky | Functionally right, visually a wireframe |
On the real screen, polish is graded even when nobody asked for it - that's the Design Engineer differentiator.
When you hit the keyboard-handling part of the combobox, say the rule before you code it: “Focus stays on the input; I move a visual active index and expose it with aria-activedescendant.” Stating the accessibility model from memory, unprompted, is exactly the craft-at-the-seam signal the unassisted screen is built to surface.
The failure mode under the clock isn't running out of time - it's spending 40 minutes on a perfect filter algorithm and shipping zero states and zero motion. Get it working end to end first, ugly, then spend the back half on edge cases and polish. A bare-but-complete component reads far better than a half-built beautiful one.
QIn an accessible combobox, the user presses the down arrow to move through options. What should happen to DOM focus?