The screenshot looks right. You open it on your phone and a button is off the edge. You submit the form with an empty field and nothing happens, no error, no spinner, just a page that sat there. None of this is a bug in the usual sense. The code does what it was asked. It was asked for the case where everything goes well.
The idea
An AI writes the screen you described, and you described the moment it works. Real screens spend most of their life in the other three states: nothing yet, waiting, and something went wrong. Those states plus two access questions, does it work on a small screen and does it work without a mouse, are the whole difference between a demo and something you can hand to a person.
EVERY SCREEN HAS FOUR STATES
---------------------------------------------------------------
EMPTY no data yet say why, and what to do first
(new account, "No invoices yet. Create one ->"
no results) never a blank rectangle
---------------------------------------------------------------
LOADING waiting on the show it within ~100ms, or the
server user clicks the button twice
---------------------------------------------------------------
ERROR it failed say what failed and what they
can do. "Something went wrong"
is not an error message
---------------------------------------------------------------
READY the happy path the one your AI built
---------------------------------------------------------------How it works
A form has two jobs: stop bad input, and explain itself. Checking the input in the browser is a courtesy to the user and nothing more, because anyone can bypass it. The check that counts runs on your server, and it has to run there even though you already wrote one in the browser. When it rejects something, the message goes next to the field that caused it, in words the user can act on. "Invalid" is not one of those words.
The small screen is the real design. Most people who open your link will open it on a phone, and a layout built at desktop width and then squeezed usually breaks in the same two places: a table that will not fit, and a fixed-width element that pushes the whole page sideways.
Without a mouse means two different users. Someone tabbing through with a keyboard, and someone
whose screen is being read aloud to them by a
Screen readerSoftware that reads a page out loud, used by people
who cannot see the screen. It reads the underlying HTML, not the picture, so a button made out of a
styled div is silent to it.. Both are served by the same thing: using the real HTML element.
A <button> is focusable, announces itself as a button, and fires on Enter. A <div onClick> looks
identical and does none of that.
What to do
- When you ask for a screen, ask for all four states in the same request. "Build the invoice list, including the empty state, a loading state, and what it shows when the fetch fails." Then make each one happen: log in as a brand new account for the empty state, and in developer tools, on the Network tab, switch the connection to a slow profile for the loading state and set it to Offline for the error state. A state you cannot reach is a state you have not seen.
- Validate on the server for every form, and return a message per field. Ask for it explicitly, or you will get browser-only validation and a generic alert. Then check it, by sending the bad data without using the form at all: open developer tools, go to the Console, and post straight to the address the form submits to with a value the page would have refused. Ask your assistant for that one-line command. If the record saves, only the browser was ever checking.
- Check on an actual phone before you call it done. The narrow device size in developer tools finds the layout overflow and nothing else. It cannot show you a tap target too small for a thumb, or the on-screen keyboard covering the field you are typing into. Open the deployed link on your own phone once. Then put the mouse down and press Tab through the page on the desktop: if you cannot see where you are, or cannot reach a control, neither can a keyboard user.
- Say "use real HTML elements, no div buttons" once in your project instructions, the file your
assistant reads at the start of every session, and it stops being a per-request fight. In Claude
Code that file is
CLAUDE.mdin the root of your project. Section 3 covers what belongs in it.
Where it breaks
Accessibility has a long tail and this page is not it. Colour contrast, focus order in a modal, form labels tied to their inputs, motion for people who get sick from it: the published rules are the WCAG guidelines, and an AI is good at applying them when you name them. What it will not do is decide for you which of your users matter.
There is also a real trap in asking for polish too early. Loading skeletons, animations and empty-state illustrations on a screen whose data model is about to change are work you throw away. Build the four states, ship it, and let the thing tell you where it is actually rough.