Front-End Craft Deep Dive
React, SolidJS, CSS and the build round
React fundamentals under the no-AI lens
After this you can write idiomatic React from memory without a model.
Cursor's coding screens let you keep autocomplete and nothing else. For a Design Engineer that means writing React the way a senior teammate would on a whiteboard: from muscle memory, narrating why each hook is the right one.
The company sells the very tool you are not allowed to use here. That is deliberate. The screen is a time-boxed read on raw skill and clarity of thought, so the bar is not just working code but code where you can explain every re-render out loud while you type.
Start with the hooks, because misusing them is the fastest way to look like someone who has only ever generated React. Each hook has a job and a failure mode.
- Hook
useState- Use it for
- Local UI state that drives the view
- When it's the wrong tool
- Derived values you can compute from props or other state - store the source, derive the rest
- Hook
useEffect- Use it for
- Syncing to something outside React: DOM, subscriptions, network
- When it's the wrong tool
- Transforming data for render or reacting to a prop change you could handle inline
- Hook
useMemo- Use it for
- Caching an expensive computation across renders
- When it's the wrong tool
- Cheap math or as a correctness crutch - it is a perf hint, not a guarantee
- Hook
useRef- Use it for
- A mutable box that survives renders without causing one: DOM nodes, timers, latest-value
- When it's the wrong tool
- State the UI should react to - refs do not trigger renders
- Hook
useCallback- Use it for
- Stabilizing a function identity passed to a memoized child or an effect dep
- When it's the wrong tool
- Every handler by reflex - wrapping a function used once adds noise and no benefit
| Hook | Use it for | When it's the wrong tool |
|---|---|---|
useState | Local UI state that drives the view | Derived values you can compute from props or other state - store the source, derive the rest |
useEffect | Syncing to something outside React: DOM, subscriptions, network | Transforming data for render or reacting to a prop change you could handle inline |
useMemo | Caching an expensive computation across renders | Cheap math or as a correctness crutch - it is a perf hint, not a guarantee |
useRef | A mutable box that survives renders without causing one: DOM nodes, timers, latest-value | State the UI should react to - refs do not trigger renders |
useCallback | Stabilizing a function identity passed to a memoized child or an effect dep | Every handler by reflex - wrapping a function used once adds noise and no benefit |
Knowing the wrong use of each is what separates fluency from recall.
Reconciliation, keys and unstable referenceswhy your component re-rendered
React re-renders a component when its state changes, its parent re-renders or its context value changes. The trap is passing a fresh object, array or function on every render: {} and () => {} are new identities each time, so a memo'd child sees changed props and re-renders anyway.
Keys tell React which list items are the same across renders. A wrong key - index in a reorderable list - makes React reuse the wrong DOM node, so input focus jumps and animations snap. Use a stable id from the data, never the array index when the list can reorder, insert or delete.
// Wrong: index as key in an editable, reorderable list
{rows.map((row, i) => <Row key={i} value={row.value} />)}
// Right: stable identity from the data model
{rows.map((row) => <Row key={row.id} value={row.value} />)}Controlled inputs and focusthe detail interviewers poke at
A controlled input drives its value from state and updates on every keystroke. An uncontrolled input owns its own value and you read it through a ref. For a polished editor UI you almost always want controlled, but you must handle the focus and caret yourself.
- Controlled:
value+onChange- predictable, validatable, but you can fight the caret if you reformat the value mid-type. - Uncontrolled:
defaultValue+ref- simpler, but the value lives in the DOM, so React state and the field can drift. - Focus management: move focus deliberately on mount, on open and on error - a popover that steals or drops focus reads as broken to keyboard users.
Effect pitfallswhere async bites
Effects are where most bugs live in an interview. The dependency array must list every reactive value the effect reads, async effects need a cancellation guard and anything you subscribe to you must clean up.
useEffect(() => {
let cancelled = false;
fetchPreview(query).then((data) => {
if (!cancelled) setPreview(data); // ignore the late response
});
return () => { cancelled = true; }; // runs before the next effect / on unmount
}, [query]);Narrate renders as you go: “this sets state, so this subtree re-renders; I'm memoizing the row callback because the list is virtualized and identity churn would re-render every row.” Cursor probes the why and a clean spoken model of renders is the signal they want.
Reaching for useEffect to derive render data is the most common tell. If a value can be computed during render from props and state, compute it there - an effect that just calls setState from other state creates an extra render and a class of stale-value bugs.
Takeaway. Hooks have one right job and a sharp failure mode each; the screen rewards code you can narrate render-by-render, not clever code you can't explain.
Self-check
QA memo'd child re-renders on every parent render even though its visible props look unchanged. What is the most likely cause?
SolidJS and the signals model
After this you can speak fluently about fine-grained reactivity, not just React.
SolidJS is named in the job description and most candidates skip it. Walking in able to reason about signals - not just recite that they exist - is a cheap, strong differentiator for this role.
Solid's core idea: a component function runs once. There is no re-render. Reactivity lives in signals and only the exact DOM expressions that read a signal re-run when it changes. React re-runs your component and diffs a virtual tree; Solid wires the value straight to the node and skips the diff entirely.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Step each dimension - this contrast is the high-value tell when Solid comes up.
// React: Counter() runs again on every click
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// Solid: Counter() runs once; only the text node updates
function Counter() {
const [count, setCount] = createSignal(0);
return <button onClick={() => setCount(c => c + 1)}>{count()}</button>;
}Note count() - a signal is a getter you call and that call is what subscribes the surrounding computation. The primitives map roughly onto hooks, but the semantics differ.
- React
useState- Solid
createSignal- What's different
- Solid returns a getter/setter pair; reading is a function call that tracks the reader
- React
useEffect- Solid
createEffect- What's different
- No dependency array - Solid tracks reads automatically and re-runs only when those signals change
- React
useMemo- Solid
createMemo- What's different
- A cached derived signal; downstream readers update only when the memo's value actually changes
- React
- Spreading state objects
- Solid
createStore- What's different
- Nested, fine-grained reactive objects with path-level updates via
setStore
| React | Solid | What's different |
|---|---|---|
useState | createSignal | Solid returns a getter/setter pair; reading is a function call that tracks the reader |
useEffect | createEffect | No dependency array - Solid tracks reads automatically and re-runs only when those signals change |
useMemo | createMemo | A cached derived signal; downstream readers update only when the memo's value actually changes |
| Spreading state objects | createStore | Nested, fine-grained reactive objects with path-level updates via setStore |
Close cousins on the surface, different mechanics underneath.
Why this matters for an editor UIsurgical updates, dense surfaces
Cursor's interfaces are dense: a file tree, a diff gutter, inline ghost text, an agent panel, all updating as you type. With a re-render model you fight to keep those subtrees from re-rendering on every keystroke. With fine-grained reactivity, a signal change touches one text node, so 60fps in a busy UI is closer to the default than a battle.
You will not be expected to have shipped Solid. A React engineer who can correctly contrast the re-render model with fine-grained reactivity signals exactly the adaptability the team is screening for - the ability to pick up the right tool for a novel UI rather than forcing one paradigm.
The gotchas that catch React devswhere the mental model leaks
- Reading a signal outside a tracking scope (an effect, memo or JSX) gets a value but creates no subscription - it will not update. Read it where reactivity should run.
- Props are not destructurable the React way:
const { value } = propssnapshots the value and loses reactivity. Readprops.valueat the point of use or usesplitProps/mergeProps. - Conditionals and loops in JSX use
<Show>and<For>, not raw ternaries and.map, so Solid can track and reuse nodes instead of recreating them. - The setter is the only way to update a signal - there is no mutate-then-rerender; you call
setCount(next)and dependents re-run.
// Broken: value is read once and frozen, reactivity lost
function Field({ value }) {
return <input value={value} />;
}
// Right: read props.value lazily, inside the tracked expression
function Field(props) {
return <input value={props.value} />;
}If Solid comes up, lead with the one-sentence contrast: “the component runs once and signals update the DOM directly, so there's no virtual-DOM diff.” Then name a concrete payoff for Cursor: a diff gutter that updates one line's marker without touching the rest of the tree.
Takeaway. Solid runs a component once and updates only the DOM that read a changed signal; the high-value tell is contrasting that with React's re-render model and naming why it helps a dense editor UI.
Self-check
Modern CSS & pixel-perfect layout
After this you can produce pixel-perfect, themeable layouts fast.
The build round judges polish and polish lives in CSS. You should reach for the right layout primitive without thinking, theme with custom properties and have opinions about the half-pixel.
Flexbox and Grid are not interchangeable. Flexbox lays out along one axis and is content-driven; Grid defines a two-dimensional structure the content slots into. Reach for Flexbox for a toolbar or a row of pills, Grid for a panel layout or anything where rows and columns must align across siblings.
- Tool
- Flexbox
- Best at
- One-axis distribution: toolbars, button rows, sidebars
- Tell that it's the wrong choice
- You're adding margins to fake a grid - the columns won't stay aligned
- Tool
- Grid
- Best at
- Two-axis structure: app shells, card layouts, form rows
- Tell that it's the wrong choice
- You only have a single row and no alignment across siblings to maintain
- Tool
- Subgrid
- Best at
- Child aligning to the parent grid's tracks (label/control rows that line up)
- Tell that it's the wrong choice
- There's nothing to align to - a plain grid is simpler
- Tool
- Container queries
- Best at
- A component that must adapt to its container, not the viewport
- Tell that it's the wrong choice
- The whole page reflows together - a media query is the honest tool
| Tool | Best at | Tell that it's the wrong choice |
|---|---|---|
| Flexbox | One-axis distribution: toolbars, button rows, sidebars | You're adding margins to fake a grid - the columns won't stay aligned |
| Grid | Two-axis structure: app shells, card layouts, form rows | You only have a single row and no alignment across siblings to maintain |
| Subgrid | Child aligning to the parent grid's tracks (label/control rows that line up) | There's nothing to align to - a plain grid is simpler |
| Container queries | A component that must adapt to its container, not the viewport | The whole page reflows together - a media query is the honest tool |
Most layout mistakes are picking the one-axis tool for a two-axis problem.
Theming with custom propertiesdark mode and density without duplication
Define semantic tokens as CSS variables and let components consume them. Dark mode then flips a handful of values, not a parallel stylesheet. color-mix() lets you derive hover and border tints from one base color, so a palette change ripples everywhere automatically.
:root {
--surface: #ffffff;
--text: #18181b;
--accent: #14b8a6;
/* derive a subtle hover from the accent, no second hex to maintain */
--accent-soft: color-mix(in oklab, var(--accent) 14%, transparent);
}
[data-theme="dark"] {
--surface: #18181b;
--text: #fafafa;
}
.btn { background: var(--accent-soft); color: var(--text); }Stacking contexts and z-index disciplinethe bug behind every broken popover
A popover that renders behind another panel is almost never a z-index number problem - it's a stacking-context problem. transform, opacity below 1, filter and will-change all create a new stacking context, trapping a child's z-index inside its parent.
- A
z-index: 9999child can still sit behind a sibling if its parent forms a stacking context with a lower index - z-index only competes within the same context. - Render overlays, menus and tooltips in a portal at the document root so they escape ancestor stacking contexts entirely.
- Keep a small set of named layer tokens (base, dropdown, modal, toast) instead of scattering raw numbers - z-index sprawl is a maintainability smell interviewers notice.
The 90 to 100 in spacingoptical, not just metric
A spacing system on a 4px grid gets you to 90. The last 10 is optical: an icon next to text often needs a hair less leading space than the grid says because its glyph has built-in padding; a chevron looks centered when nudged a pixel toward its visual mass, not its bounding box.
- Icon + label gap
- Trim 1–2px versus pure-text spacing; icons carry internal padding
- Capital-letter alignment
- Align to the cap height, not the line box, so headings sit true
- Round vs square shapes
- Circles need slight overshoot to look the same size as squares
- Punctuation hang
- Let quotes and bullets hang into the margin for a clean left edge
These are the details a Design Engineer is hired to catch.
Figma auto-layout maps cleanly onto Flexbox: direction → flex-direction, gap → gap, padding → padding, hug/fill → width: max-content / flex: 1. Pull spacing and color from the file's variables as tokens rather than eyeballing hex values and check the result against the frame at 100% and 200% zoom.
Takeaway. Pick Flexbox/Grid by axis count, theme through semantic CSS variables with color-mix and remember that a stuck popover is usually a stacking-context bug - then earn the last 10% with optical spacing.
Self-check
QA dropdown with z-index: 9999 still renders behind a neighboring panel. What's the real cause and the durable fix?
Component & design-system architecture
After this you can design reusable component APIs that scale.
The job calls out contributing reusable, maintainable components to the design system. That is systems thinking and it shows in API design long before it shows in pixels.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Each layer rests on the one below - top is what the user sees, bottom is what everything derives from.
Two patterns carry most of the load. Compound components let related pieces share implicit state through context, so the consumer composes structure instead of passing a dozen props. Headless components ship behavior and accessibility while leaving every style to the consumer.
<Select> owns state via context
<Select.Trigger> / <Select.Option> read it
Consumer arranges layout, not props
Behavior + ARIA, zero styling
Consumer owns all visuals
One a11y implementation, many looks
variant, size, tone as enums
Map to classes, not branching JSX
New variant = new entry, not a rewrite
Render as a different element
A Button that becomes a link
Keeps behavior, swaps the tag
type ButtonProps = {
variant?: "solid" | "ghost" | "outline";
size?: "sm" | "md" | "lg";
tone?: "neutral" | "accent" | "danger";
};
// classes derive from the enums - adding a tone never forks the render path
const cls = clsx(base, byVariant[variant], bySize[size], byTone[tone]);Accessibility is table stakesnot a follow-up ticket
For a Design Engineer, shipping a menu without keyboard support is shipping it broken. Interviewers will ask how a component behaves with no mouse, so build it in from the first commit.
- Roles and state: the right
role, plusaria-expanded,aria-selectedandaria-controlswired to real ids. - Keyboard model: Arrow keys move within a widget, Tab moves between widgets, Escape closes, Enter/Space activate.
- Focus trap and restore: a modal traps focus inside and returns it to the trigger on close.
- Labels: every control has an accessible name, via visible text,
aria-labeloraria-labelledby. - Respect
prefers-reduced-motion: gate non-essential animation behind it.
When to abstract and when not topremature abstraction is a graded mistake
Pulling two similar components into one configurable component too early creates a prop-soup abstraction that nobody can read and every new case bends out of shape. The healthier instinct is to wait for the third real use before generalizing and to prefer composition over a growing options object.
- Signal
- Repetition
- Abstract now
- Three real, near-identical uses exist
- Keep it inline
- Two uses that merely look similar
- Signal
- Stability
- Abstract now
- The shape has stopped changing
- Keep it inline
- Requirements still in motion
- Signal
- Prop count
- Abstract now
- A small, orthogonal prop set covers cases
- Keep it inline
- Every new case adds a boolean flag
| Signal | Abstract now | Keep it inline |
|---|---|---|
| Repetition | Three real, near-identical uses exist | Two uses that merely look similar |
| Stability | The shape has stopped changing | Requirements still in motion |
| Prop count | A small, orthogonal prop set covers cases | Every new case adds a boolean flag |
Abstraction earns its keep on the third use, not the first.
Not breaking your consumersthe part of systems thinking the JD names
- Add, don't mutate: new optional props with safe defaults keep existing call sites working.
- Deprecate with a path: warn in dev, document the replacement, give a migration window before removal.
- Treat the prop API as a contract: renaming or retyping a required prop is a breaking change, even if the visuals are identical.
When asked to design a component, sketch the API first - its props and composition - and say why a consumer would reach for it. Designing the call site before the internals is the systems-thinking signal the team is grading and it doubles as the cleanest way to handle a vague prompt.
Takeaway. Design the call site first: compound or headless primitives, a flat variant enum, accessibility built in from commit one and additive changes so you never break a consumer.
Self-check
QAn interviewer asks you to add a fourth visual style to a Button that currently uses an enum-driven variant API. What's the clean way and what would be the smell?
Designing in Cursor: the round-trip with Figma
After this you can prototype in Cursor against a real design system and hand work back to your team.
The interview screens raw React. The job is something else: at Cursor, designers and design engineers prototype directly in the product, against a live design system, and publish finished work back to Figma. Knowing that loop is what separates a Design Engineer from a React generalist.
Start in the Agents window, not the editor. Over the last few releases Cursor shifted its primary surface away from the VS Code-style file tree toward an agent-first view. Reach it with the Agents window button top-right, or Command+Shift+P then "Agents window", and toggle back anytime. For a designer who doesn't want a debugger and a file tree staring back, this is the place to live.
On the move toward an agent-first surface:
“I think over the past couple months we've seen a departure from this type of view and we've seen more of a focus on agents.”
Interactive diagram. Step through it with the Next and Previous controls below, or Tab to a region to read its detail.
Figma is the source of truth; Cursor is where the design moves forward.
Pull a design system in, prototype freely in Cursor, publish finished components back out — Figma stays the source of truth engineers build from.
Inbound: pull the design system intokens, type, color — not a guess
Paste a Figma file link straight into the prompt: "use the design system at this link to inform the design of my dashboard, make any relevant color and typography changes." Cursor's Figma MCPModel Context Protocol. A standard that lets an AI agent pull in context from outside the repo, like Jira tickets or internal docs. reads the tokens, typography, colors and variable definitions, screenshots the frames and applies them to the live code. The point of feeding it the real file is that it stops Cursor from hallucinating its own icons, kerning and padding and quietly rebuilding your design system from scratch.
Prototype by clicking the rendered UIdesign mode + @browser
Type @browser in a prompt and Cursor runs your built app in a pane inside the Agents window. There's a design mode button in that pane: toggle it on, click any div in the running app, and that element plus a screenshot is handed to the agent as context. Then you just describe the change — "make this font 3x as big and bright red" — and Cursor edits the code and re-renders live. Command+L adds an element to context, so you can select several divs at once and describe multiple changes in one prompt; Cursor identifies the distinct components and edits each.
It's a very quick way to prototype and rapidly iterate without having to go in, find the div and hand-edit the CSS. You change the UI by pointing at it, the way you'd mark up a screenshot.
The browser pane isn't just for looking. Ask the agent to take control and test interactions — "test that the category slider works, take control and make sure it works" — and Cursor will scroll, click and drag sliders on its own. That in-browser agent QA is how a lot of folks actually do QA at Cursor.
Outbound: publish the component back to Figmaclose the loop
When something is worth sharing, select the rendered component in design mode, Command+L it into chat, and say "publish this component back to Figma" with the target file link so it knows where to land. Cursor recreates the component in Figma with the same colors, fonts and width, split into proper layers — published-back layers show up in purple. Now a designer can prototype in Cursor and hand a clean, collaborator-friendly Figma file back to the team.
On the bidirectional workflow:
“I'm able to get context from Figma into Cursor, and then I'm able to get anything that I prototype or I build in Cursor back to Figma. And so, it's this beautiful dance.”
Handoff, sharing and version controlthe parts designers actually ask about
At Cursor, designers feel empowered to do their prototyping inside the product, then publish anything that needs collaboration back to Figma. Once it's in Figma, engineers pick it up and Figma is the source of truth they build from. That round-trip is what makes handoff seamless and cuts iteration time.
- Sharing a live prototype
- No built-in way yet to send an interactive Cursor prototype to outside stakeholders. Today you deploy via Vercel or GitHub Pages, or publish to Figma and let the file be the shareable artifact. A known gap, with better ways likely coming.
- Versioning outside Figma
- Use Git — and you don't have to learn any commands. Just ask: "save this version of my codebase" or "push this up to a branch so I can get back to it later." Cursor runs the commands for you.
- Aligning before engineering
- A shared plan or PRDProduct Requirements Document. The spec describing what to build and why. is the guideline a PM and designer both prototype against before talking feasibility with engineers.
The honest gaps matter as much as the wins — name them in an interview.
Plan mode as your paper traildesign with a frontier model, build with the plan
Flip on the plan pill and run it on a frontier planning model (GPT-5.x) and Cursor writes a document rather than code — a feature roadmap that states the current baseline, then a recommended feature set with realism and intelligence upgrades. The plan is your documentation. Copy a section into chat and say "implement this part of the plan" or "I like this feature, go ahead and build it," and you implement it piece by piece.
“The plan is your documentation or paper trail for what you want to build, and then you can go ahead and build off of the plan.”
Which model for design workexecution vs planning vs adherence
There's no single answer, but there are clear leanings. Cursor's own designers love GPT-5.x and Gemini, with Gemini getting a lot of love for design specifically. ComposerCursor's own fast coding model, tuned for the editor and priced well below frontier models; the recommended day-to-day model for executing a plan. — Cursor's in-house code model — is the speed pick for rapid UI iteration and diffs, which is why it's the default reach when you're clicking through changes. The one firm rule: don't plan with Composer. It's built for execution, not prose or roadmaps.
Interactive widget. Tab through its controls; the result updates in the panel below as you change them.
Plan with a thinker, execute with a fast model.
Composer for fast UI execution, a frontier model (GPT-5.x / Gemini) for planning and tighter spec adherence, Auto to route per task.
On ComposerCursor's own fast coding model, tuned for the editor and priced well below frontier models; the recommended day-to-day model for executing a plan.'s scope, quoting Cursor's CTO:
“ComposerCursor's own fast coding model, tuned for the editor and priced well below frontier models; the recommended day-to-day model for executing a plan. won't write poetry, it won't do your taxes, but it will write very good code.”
Use that as a decision rule. When Cursor's output drifts from your Figma source, switch up to a more powerful model — stronger models adhere better to a file's exact tokens and spec. When you're just iterating fast on a layout, drop back to ComposerCursor's own fast coding model, tuned for the editor and priced well below frontier models; the recommended day-to-day model for executing a plan. for the speed.
Make the design system persistentskills, not vibes
Pointing Cursor at a Figma file once works for one session. To make it stick, put the design context into a skill — a persistent Markdown file that shapes how Cursor behaves and can reference scripts and other MD files. Cursor reads design.md files too, but the guidance is to fold them into a skill so they're reliably visible and used.
- "Every time you make changes, check the Figma design system in this skill and adhere to it."
- "After any design change, use the browser and test it yourself."
- Hand it icons, kerning and padding context so Cursor stops hallucinating its own and remaking your design-system elements.
While we're on primitives: the cleanest plain-language definition of a pluginA Cursor marketplace package that bundles MCP servers and skills (sometimes sub-agents and hooks); one click installs all of it into your Cursor instance. is MCPs and skills packaged together and published on Cursor's platform. Sometimes a plugin also bundles sub-agents and hooks, but for the most part it's MCPs plus skills.
Start a new agent for every new task. Continuing the same task in one session is fine, but a genuinely new one — prototyping a different feature — should get a fresh session so context stays isolated and you don't confuse the model or invite scope creep. The mantra: be specific, be precise, stay isolated.
If the conversation turns to how you'd actually work day-to-day, describe the round-trip out loud: pull the design system from Figma, prototype in the Agents window with design mode and @browser, keep the system honest with a skill, then publish finished components back to Figma for engineers to build from. Knowing the loop — including its gaps, like sharing live prototypes — reads as someone who has done the job, not just the LeetCode.
Takeaway. Design in Cursor as a round-trip: pull the Figma design system in, prototype by clicking the rendered UI in the Agents window, keep it on-spec with a skill, then publish components back to Figma as the engineering source of truth — using ComposerCursor's own fast coding model, tuned for the editor and priced well below frontier models; the recommended day-to-day model for executing a plan. for speed and a frontier model for planning and adherence.
Self-check
QYou're prototyping a dashboard in Cursor and your team uses Figma as the source of truth. How do you keep Cursor's output on-spec, and how do you get a finished component back to the designers?
Acing the FE build round
After this you can ship a component that is correct AND polished under time pressure.
The build round asks for one component or interaction, live or as a short take-home and grades it on correctness and polish. The win is sequencing: get it working end-to-end, then spend your remaining time where craft shows.
The most common failure is polishing before the thing works - animating a dropdown that doesn't yet open with the keyboard. The second is the opposite: a correct component that looks like a wireframe, from a candidate who never budgeted time for the finish.
- 1Clarify and pick a state strategy. Restate the requirement, then choose where state lives (local state, a ref or lifted) before writing JSX. Say the choice out loud.
- 2Get it working end-to-end first. Make the happy path real - open, select, close - even if it's unstyled. A working ugly component beats a beautiful broken one.
- 3Layer in the states that signal craft. Focus rings, hover, active, disabled, plus empty, loading and error. These are where a Design Engineer is judged.
- 4Add motion last and make it earn its place. Animate transform and opacity for 60fps, keep it under ~200ms and gate it behind
prefers-reduced-motion. - 5Reserve the last 10 minutes for self-review. Re-read the API like a teammate's PR, tidy names and narrate what you'd do with more time.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
The gate is real: don't cross into polish until the happy path actually works end-to-end.
Edge cases, said out loudthinking like a feature owner
You will not have time to handle every edge case, so name them as you go. Voicing the case you're deferring shows the ownership instinct Cursor screens for even when the clock beats you to the implementation.
- Long content
- Truncation, wrapping and overflow scroll without layout jump
- Keyboard-only
- Full operation with no mouse: arrows, Tab, Escape, Enter
- RTL
- Logical properties (
inline-start) so layout mirrors correctly - Empty / error / loading
- A real state for each, not a blank flash or a frozen control
Code they read like a teammate's PRnaming and API are part of the grade
Interviewers read your component the way they'd review a colleague's pull request. Clear prop names, a sensible default and no dead code matter as much as the behavior. Tighten the surface before time runs out.
type ComboboxProps<T> = {
items: T[];
value: T | null;
onChange: (next: T | null) => void;
getLabel: (item: T) => string;
placeholder?: string;
disabled?: boolean;
};
// Generic over T, controlled, one obvious way to render a label.“It's working end-to-end with keyboard support. With more time I'd add a fade on open behind prefers-reduced-motion, virtualize the list past ~200 items and write a test for the empty state. The long-content case truncates with a tooltip - I'd verify that against real data.”
Don't go silent while you code. The build round is as much about how you think as what you ship, so a steady narration of decisions and trade-offs keeps a half-finished component scoring well - silence makes even a complete one read as a black box.
Takeaway. Working-then-polished, in that order: lock the state strategy, finish the happy path, then spend real time on focus/empty/error states and restrained motion - narrating edge cases you defer.