Have a go first. It’s the real component, not a picture of it.
Everyday
Ways out
Edge cases
I’m planning a trip to London, can you give me some hotel recommendations?
Happy to. A few quick questions first, so I search for the right places.
The playground needs JavaScript to run the real component.
Try it in a full chat app, on your phone too
Backstory
A favourite, not a flagship
- I first wanted to build an artifact editor like Claude’s, with collaborative editing. It’s a huge build, and I’d already built something like it at work. So I picked something smaller and more personal.
- Claude’s question card is one of my favourites. It rescues a lazy, vague prompt, and lazy people like me love it. I’d used it a lot, and was always curious when Claude decides to show it, and when it doesn’t.
Artifact editor
- Collaborative editing, a huge build
- Built one of its kind at work already
- Weeks, not a weekend
vs
The question cardthe pick
- Lots of variants, states and cases
- A personality of its own
- A personal favourite
Research
Knowing when is the real work
- I started with a vague Claude research about the card itself: how other frontier tools do it, and what’s public to learn from. That’s where I met its name, an ambiguity resolver. Sounds wild, right? I asked Claude to write the design tensions down in a learnings file as we went.
- That’s when it clicked. The real moat isn’t the card. It’s teaching the agent when to use it, and when not to. And the card has many personalities: several questions or one decision, all at once or strictly one by one.
Homework
Borrow the feel, not the pixels
- Reading done, I took the card itself apart. I lined it up against the
AskUserQuestiondocs, then against how Cursor, ChatGPT, MCP and LangGraph ask. - Three things stuck. Multi-select must never move on by itself. Skip and close mean different things. And when to ask matters as much as how, so the README ships a prompt that tells the model when.
↑↓ to move · Enter to pick · Esc to skip
- 1The question, in plain wordsOne decision per screen, with no form to scan.
- 2“2 of 4”How many are left, so it never feels endless.
- 3Numbered keysThe number is the shortcut.
- 4An arrow on the active rowIt shows where Enter goes.
- 5“Something else”The escape hatch, always one row, never hidden.
- 6Skip, and ×Two ways out. I wanted each to mean something different.
And what I took from others
-
Cursor plan mode
Multi-select must never move on by itself. Theirs did, after one click.
-
ChatGPT deep research
Ask before the expensive work, not halfway through it.
-
MCP elicitation
Accept, decline and cancel are three different answers.
-
LangGraph interrupts
The agent can choose to ask, or a step can always need a human.
-
Shape of AI
Follow-ups belong before generation, short and set apart.
Design call · meaning
Every way out means something
- I wanted each exit to tell the agent something different, so it can say what it did about it. Skip means “say what you assumed”. Decide for me means “say what you chose”.
- So Decide for me takes Skip’s place instead of sitting next to it, to avoid the overload. And × became minimise, so nothing is lost. A message typed while it’s folded is the answer.
You
The agent hears
Skip
this question
“No preference. Use your judgement, and say what you assumed.”
status: skipped
Decide for me
in Skip’s place, when offered
“Up to you. Pick what fits, and say what you chose.”
status: delegated
Your own words
fold the card, type, send
“This message is the answer, along with the picks so far.”
delivery: composer
Skip all
stop asking, just go
“Go ahead. Use your judgement on all of it.”
status: skipped
× Close
answers gone
became minimise: a pill, with nothing lost
Simplify
Ask what each control is for
- After v1 I asked for a draft mode: pick, get the answer prefilled, add context, then send. It came back as three levels of opt-in, an Edit first button and a prefill strip. Reviewing it, I couldn’t say what Edit first was for.
- So we cut it: one way to answer, the card, and one way out to your own words, the composer. Later Claude designed branching, and I said no to that too, to build a top-notch core first.
Draft mode
- Send, Draft or Choice, per question
- Edit first and Add to message
- A prefill strip, then Keep or Update draft
vs
One way to answer, one way outthe call
- Review lets you edit each answer
- Minimise gives the composer back
- A compose option for answers worth writing
scroll the real board ↓
Process
From a board to a playground
- Plan and visuals came first: research, a written plan, a board of every state, then a build plan we agreed on before any code. Each round was snapshotted into
process/, and never edited after. - Then I hit a wall: I couldn’t judge a shadow, a grey or a 120ms confirm from a drawing. So the board was frozen in round 6, and every call after it was made on the real component, in a playground like the one above.
On paper · round 0 to 5
-
Round 0 · 10 Oct
Homework
Claude’s card taken apart, five other systems, and the research on when models should ask
-
Rounds 1 to 3
A plan and a board
30+ frames in 8 sections, every state. Draft mode added in round 2, cut in round 3
-
Naming
clarify-card → agent-clarifier
A noun, named after the job, not the look. It’s also a pill, a docked tray and a record
-
Rounds 4 and 5
Scope lock
Branching said no to. A build plan agreed, and DESIGN.md started: a reason for every value
On the real component · round 6 onwards
-
Round 6
The playground
The board frozen. Every call after this is made on the real thing, in a fake chat
-
M1 to M7
The build
Model, element, both flows, keyboard, screen readers, converters for Claude and MCP, README
-
Rounds 9 to 21 · 11 Oct
Audits and details
One skill at a time, then my own doubts from using it, one per round
-
Rounds 22 to 26 · 12 Oct
Weight, release and a chat demo
Size measured against 25 packages, a review that read it like a stranger, then npm, Svelte 5 and a phone-first chat demo
With AI
I make the calls, Claude does the legwork
- We worked as a pair in Claude Code. Claude researched, drafted, built and tested. I set the direction and made the calls, from options with a recommendation, one round at a time. When I delegated a batch of calls in round 8, I audited them after.
- For the audits I brought in skills from people whose craft I trust: Emil Kowalski’s for motion, and Jakub Krehel’s for the interface. Each ran read-only first. I picked. Then Claude built it, and broke each fix on purpose to prove its test could fail.
One round
- 1 Me A brief, or a doubt from using it
- 2 Claude + a skill A read-only audit, frozen before any fix
- 3 Me Pick from options, with a recommendation
- 4 Claude Build it, test it, break the fix on purpose
- 5 Both Snapshot, journal, commit
…and round again, one skill at a time
The skills
| Round | Skill | What it found | Turned down |
|---|---|---|---|
| Round 9Motion |
find-animation-opportunities
Emil Kowalski
|
FoundA toast that swapped in unnoticed, and the doc saying 200ms while the code ran 160ms. | Turned downFive ideas: fills on rows, a pulse on the pill, an error shake and more. |
| Round 10Craft |
emil-design-eng
Emil Kowalski
|
FoundButtons that didn’t answer the hover, and an Undo timer that kept running in a hidden tab. | Turned downEase-in-out for the height. It would lag behind the content. |
| Round 11Interface |
better-interface
Jakub Krehel
|
FoundSix reviewers in parallel, 10 high: contrast themes lost every ring, Send dropped focus, the toast read “Und” at 320px. | Turned downIts own fix for one finding. It would have broken Undo. |
| Round 13Stress |
break
Jakub Krehel
|
Found34 worst cases on one page. 6 broke, and the worst was made by round 11’s own fix. | Turned downStacked combining marks. Abusive input, not a real case. |
| Round 15Checkbox |
animate + review-animations
Emil Kowalski
|
FoundA 0.8px press and a 120ms tick, for the mouse only. | Turned downMotion on keyboard toggles, on unticking, and a bounce. |
| Round 20Hover |
7 practical animation tips
Emil Kowalski
|
FoundHover was already instant. But an arrow stayed on a row the mouse had left. | Turned downHiding the keyboard’s arrow while a mouse rests on the card. |
| Round 23Release |
code review
Claude Code
|
FoundFour reviewers read the package as a stranger would: 44 findings, 9 serious, three README recipes broken. | Turned downLong MCP lists. Documented instead, outside the scope lock. |
| Round 23Cleanup |
/simplify
Claude Code
|
FoundA regression in one of the review’s own fixes, caught before it shipped. | Turned downTwo cleanups that would have changed behaviour. |
Design call · colour
The user’s decision is the loudest thing
- I asked why the card mixed black and blue. The answer found a real problem: blue had four jobs on one card, and the agent’s Recommended badge looked like my pick.
- So we prototyped a monochrome card, and I kept the blue anyway. The badge went grey instead. The same habit, see the options on the real thing, then pick, settled the row spacing and the pencil too.
Blue had four jobs on one card, and the agent’s Recommended badge sat right next to my pick. Claude prototyped two monochrome cards from a shadcn reference I liked.
Look at the picked key, the Recommended badge and the focus ring.
The callKept the blue: it says “your pick” best. The badge and the “Asked by” dot went grey.
Two picks next to each other looked like one shape, and so did a pick next to a hovered row.
Look between the two ticked rows, and the hovered one under them.
The callGap: one rule for every case, each answer stays its own thing, and no row changes height.
With “Something else” open, its pencil stayed a grey square on a picked row. I couldn’t see the first fix at all.
Look at the pencil key on the “Something else” row.
The callB: typing in “Something else” is picking it, so it looks like its picked neighbours.
Details
Small things, on purpose
- Most rounds started with a doubt from using it: an arrow that lingered, a header that lost its padding, a checkbox that felt plain. Each got a measurement before a fix, a test that fails without it, and a before and after.
- I wanted it to feel calm, and calm is a long list of small things. Every default now has a written reason in
DESIGN.md. If a value has no reason, it’s a guess, and guesses get fixed.
Round 17
A limit that informs, not scolds
“Up to 2 picks” was a black toast, louder than the picks themselves. Black is for toasts you can press, so now it’s quiet ink text.
Look at the footer, left of Skip.
Round 21
Back goes where the card doesn’t move
The card sits at the bottom, so its header is the edge that moves between steps. Back moved to the footer, and the dashes now show what’s answered.
Look at the top-left corner, then the bottom-left.
Round 14
“Something else”, typed in place
It used to open a second box under the row. Now the row becomes the field. On single choice, once you type, the other options fade to grey, never see-through.
Look at the last row.
Round 21
The number key answers the hover
The key was 2% away from the hover fill, so it vanished into it. Now it steps toward ink, mixed from the tokens, so a theme’s key follows.
Look at the 1 key on the hovered row.
Round 18
A stuck header keeps its padding
A margin was collapsing out of the sticky header, so “Asked by” touched the card’s edge once you scrolled. The fix was one line: display: flow-root.
Look above “Asked by”.
Round 20
The arrow travels with its partner
Moving the mouse across rows left an arrow on a row it had already left. Now the arrow shows only with its partner: the fill for a mouse, the ring for the keyboard.
Look at the right end of row 1, in the second and third frames.
Round 9
The pill that turned into an oval
Slowed down 10 times, the fold showed the minimised pill stretched into an oval mid-way. No test was looking for that. Now the pill only fades in.
Mid-fold, before
0 of 4 answered · ReopenAfter
0 of 4 answeredReopen Or reply in your own words…Round 15
The checkbox animation that didn’t exist
I wanted the feel of Claude’s checkbox. Frame by frame, it fills in one frame: the feel was my screen recorder’s click circle. We animated ours anyway, by Emil’s rules: a 0.8px press and a 120ms tick, mouse only.
Watch the tick draw in, left to right, over the first 60ms.
Weight
Weigh it before you diet
- I wanted it light, so nobody thinks twice about installing it. Claude measured first: 25 comparable packages, built with the same settings. Every safe saving added up to about 0.6 KB.
- So the honest result was a better target, not a smaller number: about 21 KB gzipped, among single-purpose widgets that do less, and nothing at page load if it loads on first use. The playground above does exactly that.
Small and polished
Single-purpose widgets
Chat kits it would sit beside
and 0 KB at page load, if it loads on first use
Misses & lessons
What I’d tell day-zero me
-
MissTrusted 100% test coverage. Two tests checked either side of a limit, never the limit itself.
LessonTest at the boundary. Then break the code on purpose, and watch a test fail.
-
MissTrusted an “after” screenshot. The gap looked right only because a focus ring drew it.
LessonA screenshot can agree with you for the wrong reason. Measure the thing itself.
-
MissAssumed the audits still held. Two rounds of my own changes later, an accessibility fix had quietly gone.
LessonAudits expire. Re-run them after every change to the parts they judged.
-
MissTested against the source for 22 rounds. Three README recipes didn’t work, and some TypeScript users got no types.
LessonRead it as a stranger. Install the package, and follow your own README.
What’s next
From a weekend to a day?
- The end goal is bigger than one card: a skill and a knowledge base for crafting UX elements like this, faster. This one took a whole weekend of learning and care. Can the next one take a day?
- The first two are out: a skill for the clarifier itself, when to use it, when not to, and which variant fits, and one for the design engineering habits from this build. Now anyone can check their agent is using it right.
The skills
- craft-componentBuild a component the careful way. Brief, plan, milestones, audit, release on a go.
- ask-clarifierWhen to ask, when not to, and which question type fits.
For the card itselfA VoiceOver pass, and a real phone with its keyboard.
Ask only when it matters, and make asking feel calm. That’s the card. Learn it once, and craft the next one faster. That’s the exercise.
Every round, with my brief, the calls and the renders, is in the journal on GitHub