How would you build a reusable UI component in React?
Open-ended 'build a UI component' prompt. Approach: clarify the spec (what does it do, what props, what variants), sketch the API first, build the simplest working version, then layer accessibility, keyboard support, controlled/uncontrolled modes, and edge cases. Examples: dropdown, modal, tabs, tooltip. Lead with the API; the implementation follows.
An open prompt. The interview is testing how you scope, sketch an API, and progressively layer concerns.
Step 1 — clarify the spec
Don't dive in. Ask:
- What component? (Dropdown, modal, tabs, tooltip — let them name it.)
- What props matter? (controlled vs uncontrolled, default state, callbacks.)
- Variants? (sizes, colors, with/without close button.)
- Accessibility expected? (ARIA, keyboard, focus trap.)
- Composition style? (compound vs single-component-with-props.)
Step 2 — sketch the API first
Before writing the body, write the call site.
// Compound (Radix-like)
<Dropdown>
<Dropdown.Trigger>Open</Dropdown.Trigger>
<Dropdown.Menu>
<Dropdown.Item onSelect={save}>Save</Dropdown.Item>
<Dropdown.Item onSelect={del}>Delete</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
// Single-component
<Dropdown
trigger={<button>Open</button>}
items={[
{ label: 'Save', onSelect: save },
{ label: 'Delete', onSelect: del },
]}
/>Compound is more flexible (custom triggers and items); single-component is simpler.
Step 3 — implement the minimum
For a dropdown:
function Dropdown({ trigger, items }: Props) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const onClick = (e: MouseEvent) => {
if (!ref.current?.contains(e.target as Node)) setOpen(false);
};
if (open) document.addEventListener('mousedown', onClick);
return () => document.removeEventListener('mousedown', onClick);
}, [open]);
return (
<div ref={ref} style={{ position: 'relative' }}>
<button onClick={() => setOpen(o => !o)}>{trigger}</button>
{open && (
<ul role="menu">
{items.map(it => (
<li key={it.label} role="menuitem">
<button onClick={() => { it.onSelect(); setOpen(false); }}>
{it.label}
</button>
</li>
))}
</ul>
)}
</div>
);
}Step 4 — layer concerns
Keyboard: Esc closes, ArrowDown moves focus, Enter selects.
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') setOpen(false);
// ... arrow keys
};
if (open) document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [open]);Controlled mode:
function Dropdown({ open: openProp, onOpenChange, ...rest }: Props) {
const [internalOpen, setInternalOpen] = useState(false);
const isControlled = openProp !== undefined;
const open = isControlled ? openProp : internalOpen;
const setOpen = isControlled ? onOpenChange : setInternalOpen;
// ...
}ARIA:
- Trigger:
aria-haspopup="menu",aria-expanded={open}. - Menu:
role="menu",aria-labelledby={triggerId}. - Items:
role="menuitem".
Portal: render menu in a portal to escape overflow: hidden ancestors.
Positioning: use floating-ui (@floating-ui/react) for collision-aware placement.
Step 5 — testing
test('opens on click and closes on outside click', async () => {
render(<Dropdown trigger="Open" items={[{ label: 'Save' }]} />);
await userEvent.click(screen.getByRole('button', { name: 'Open' }));
expect(screen.getByRole('menu')).toBeInTheDocument();
await userEvent.click(document.body);
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
});What interviewers look for
- Clarifying questions before writing code.
- API design that's flexible without being overengineered.
- A11y mention — even if you don't implement everything, name what you'd add.
- Controlled/uncontrolled awareness — design for both.
- Composability — compound components > 100-prop single component.
- State machine awareness for non-trivial UI (combobox, datepicker).
Reference implementations
Radix UI, Headless UI, Ark UI, React Aria. All open source — read their source for production patterns.
Follow-up questions
- •How do you handle controlled vs uncontrolled state in a single component?
- •When would you reach for a portal?
- •What ARIA attributes are required for a menu?
Common mistakes
- •Diving into code before clarifying the spec.
- •Skipping a11y and keyboard — interviewers notice.
- •Overengineering the API with too many props.
Performance considerations
- •Most UI components are tiny. Optimize only when measured — typically only the menu list itself, if very long. Memoize items if you're passing an array prop that's recreated each parent render.
Edge cases
- •Menu overlapping screen edge — needs floating-ui or manual collision detection.
- •Nested menus — focus management gets harder.
- •Touch devices — outside-click via mousedown alone misses touch.
Real-world examples
- •Radix UI / Headless UI / Ark UI / shadcn/ui — production-grade implementations of every common UI primitive. They handle ARIA, focus, RTL, animation, portal, controlled/uncontrolled, polymorphic types.