{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"enough-html-and-css-to-audit","title":"Enough HTML and CSS to audit the screen","url":"https://addaly.com/learn/building-apps-with-ai/enough-html-and-css-to-audit","position":22,"of":87,"minutes":8,"answer":"Check that a form input's name matches what the server reads, that controls are real buttons and labels, and that the layout survives 360 pixels and the Tab key.","keyIdea":"Check that a form input's name matches what the server reads, that controls are real buttons and labels, and that the layout survives 360 pixels and the Tab key."},"body":{"format":"markdown","text":"## You are reading, not authoring\n\nYou do not need to write CSS. You need to look at a generated screen and answer three questions: what is this element, why is it there, and why is it in the wrong place. That is an afternoon's worth of vocabulary.\n\n## HTML is a tree of labelled boxes\n\n```html\n<form action=\"/api/bookings\" method=\"post\">\n  <label for=\"name\">Your name</label>\n  <input id=\"name\" name=\"customerName\" required />\n  <button type=\"submit\">Book appointment</button>\n</form>\n```\n\nFive things to notice, all of which matter more than they look.\n\n**`name=\"customerName\"`** is what the server receives. If the server reads `customer_name`, the value arrives as nothing and the booking is saved with a blank name. This mismatch is a real and frequent AI bug, and it is invisible until you look at both sides.\n\n**`required`** is a browser convenience. It is not validation. Anybody can remove it, or send the request without using your form at all. Server-side validation is a separate thing that must also exist.\n\n**`<label for=\"name\">` tied to `id=\"name\"`** is what lets a screen reader announce the field, and what makes tapping the label focus the input. Generated forms often use a styled `<div>` instead, which looks identical and is unusable with assistive technology.\n\n**`<button type=\"submit\">`** rather than a clickable `<div>`. A real button works with the keyboard, is announced correctly, and submits on Enter. A div does none of that until somebody adds three lines of JavaScript to fake it.\n\n**The semantic elements** (`<form>`, `<nav>`, `<main>`, `<h1>`) carry meaning. A page built entirely from `<div>` renders identically and is much harder for anybody not using a mouse and eyes.\n\n## CSS: the four things that explain most layout\n\n**The box model.** Every element is content, then padding inside its border, then margin outside it. \"Why is there a gap I did not ask for\" is nearly always margin.\n\n**Flex and grid.** `display: flex` arranges children in a row or column; `display: grid` in two dimensions. Almost every modern layout is one of these, and when something is stacked that should be side by side, this is the property to look at.\n\n**Position.** `static` is the default flow. `relative` nudges from that spot. `absolute` positions against the nearest positioned ancestor. `fixed` sticks to the viewport. When something floats over the wrong thing, or a modal appears behind the page, position and `z-index` are the cause.\n\n**Responsive breakpoints.** `@media (min-width: 768px)`, or in Tailwind the `md:` prefix. `class=\"flex-col md:flex-row\"` means stacked on a phone, side by side on a laptop. Reading that one pattern lets you check a layout for phones without opening one.\n\n## Tailwind, since you will meet it\n\nGenerated interfaces are usually Tailwind, which puts the styling in the class attribute:\n\n```html\n<div class=\"flex flex-col gap-4 p-6 md:flex-row md:items-center\">\n```\n\nFlex container, stacked vertically, 1rem between children, 1.5rem of padding, becoming a centred row at 768 pixels and above. It looks noisy and it has one real advantage for this course: the styling is next to the element, so you can read a component's appearance without opening a second file and without wondering which rule wins.\n\n## The five-minute screen audit\n\nFor any screen a model just built:\n\n1. Narrow the browser window to 360 pixels. Does anything scroll sideways, overlap, or fall off?\n2. Press Tab repeatedly. Does focus move through every control in a sensible order, and can you see where it is?\n3. Submit the form with the keyboard only. Enter should work.\n4. Check that every input has a visible label, not just placeholder text. Placeholders vanish when typing starts and are invisible to some assistive technology.\n5. Zoom the browser to 200 percent. Text should reflow, not get cut off.\n\nFive minutes, no tools installed, and it catches the majority of what makes a generated interface unusable for somebody who is not you on your own laptop.\n\n## Free ways to go further\n\nMDN Web Docs is the reference professionals actually use and it is free. The browser's own element inspector, covered two lessons from now, shows you which CSS rule is producing any given pixel. Between those two you can answer nearly every layout question without asking anybody."},"check":{"question":"A generated form saves bookings with an empty customer name, though the field is filled in on screen. What is the most likely cause?","options":[{"text":"The `required` attribute is missing, so the browser allowed an empty submission.","correct":false,"tempting":"Would prevent an empty field being submitted, but the field was not empty; the value was lost between the form and the server."},{"text":"The input's `name` attribute does not match the key the server reads from the request.","correct":true},{"text":"The label is not associated with the input, so the value was not captured.","correct":false,"tempting":"Confuses an accessibility association with data binding; labels affect announcement and focus, never the submitted value."},{"text":"The database column is too short and truncated the value to nothing.","correct":false,"tempting":"Truncation shortens a value rather than emptying it, and would raise an error in most databases rather than silently storing blank."}]},"prev":{"slug":"the-shape-of-a-project","title":"The shape of a project you did not lay out","url":"https://addaly.com/learn/building-apps-with-ai/the-shape-of-a-project"},"next":{"slug":"enough-javascript-to-review","title":"Enough JavaScript to review a change","url":"https://addaly.com/learn/building-apps-with-ai/enough-javascript-to-review"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Enough HTML and CSS to audit the screen', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/enough-html-and-css-to-audit (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Enough HTML and CSS to audit the screen","courseTitle":"Building Apps with AI","module":{"index":3,"slug":"reading-and-checking","title":"Reading and checking what came back"},"answer":null,"keyIdea":"Check that a form input’s name matches what the server reads, that controls are real buttons and labels, and that the layout survives 360 pixels and the Tab key.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"you-are-reading-not-authoring","runs":[{"text":"You are reading, not authoring"}]},{"t":"p","runs":[{"text":"You do not need to write CSS. You need to look at a generated screen and answer three questions: what is this element, why is it there, and why is it in the wrong place. That is an afternoon’s worth of vocabulary."}]},{"t":"h2","id":"html-is-a-tree-of-labelled-boxes","runs":[{"text":"HTML is a tree of labelled boxes"}]},{"t":"code","lang":"html","label":"HTML","text":"<form action=\"/api/bookings\" method=\"post\">\n  <label for=\"name\">Your name</label>\n  <input id=\"name\" name=\"customerName\" required />\n  <button type=\"submit\">Book appointment</button>\n</form>"},{"t":"p","runs":[{"text":"Five things to notice, all of which matter more than they look."}]},{"t":"p","runs":[{"text":"name=\"customerName\"","b":true,"code":true},{"text":" is what the server receives. If the server reads "},{"text":"customer_name","code":true},{"text":", the value arrives as nothing and the booking is saved with a blank name. This mismatch is a real and frequent AI bug, and it is invisible until you look at both sides."}]},{"t":"p","runs":[{"text":"required","b":true,"code":true},{"text":" is a browser convenience. It is not validation. Anybody can remove it, or send the request without using your form at all. Server-side validation is a separate thing that must also exist."}]},{"t":"p","runs":[{"text":"<label for=\"name\">","b":true,"code":true},{"text":" tied to ","b":true},{"text":"id=\"name\"","b":true,"code":true},{"text":" is what lets a screen reader announce the field, and what makes tapping the label focus the input. Generated forms often use a styled "},{"text":"<div>","code":true},{"text":" instead, which looks identical and is unusable with assistive technology."}]},{"t":"p","runs":[{"text":"<button type=\"submit\">","b":true,"code":true},{"text":" rather than a clickable "},{"text":"<div>","code":true},{"text":". A real button works with the keyboard, is announced correctly, and submits on Enter. A div does none of that until somebody adds three lines of JavaScript to fake it."}]},{"t":"p","runs":[{"text":"The semantic elements","b":true},{"text":" ("},{"text":"<form>","code":true},{"text":", "},{"text":"<nav>","code":true},{"text":", "},{"text":"<main>","code":true},{"text":", "},{"text":"<h1>","code":true},{"text":") carry meaning. A page built entirely from "},{"text":"<div>","code":true},{"text":" renders identically and is much harder for anybody not using a mouse and eyes."}]},{"t":"h2","id":"css-the-four-things-that-explain-most-layout","runs":[{"text":"CSS: the four things that explain most layout"}]},{"t":"p","runs":[{"text":"The box model.","b":true},{"text":" Every element is content, then padding inside its border, then margin outside it. “Why is there a gap I did not ask for” is nearly always margin."}]},{"t":"p","runs":[{"text":"Flex and grid.","b":true},{"text":" "},{"text":"display: flex","code":true},{"text":" arranges children in a row or column; "},{"text":"display: grid","code":true},{"text":" in two dimensions. Almost every modern layout is one of these, and when something is stacked that should be side by side, this is the property to look at."}]},{"t":"p","runs":[{"text":"Position.","b":true},{"text":" "},{"text":"static","code":true},{"text":" is the default flow. "},{"text":"relative","code":true},{"text":" nudges from that spot. "},{"text":"absolute","code":true},{"text":" positions against the nearest positioned ancestor. "},{"text":"fixed","code":true},{"text":" sticks to the viewport. When something floats over the wrong thing, or a modal appears behind the page, position and "},{"text":"z-index","code":true},{"text":" are the cause."}]},{"t":"p","runs":[{"text":"Responsive breakpoints.","b":true},{"text":" "},{"text":"@media (min-width: 768px)","code":true},{"text":", or in Tailwind the "},{"text":"md:","code":true},{"text":" prefix. "},{"text":"class=\"flex-col md:flex-row\"","code":true},{"text":" means stacked on a phone, side by side on a laptop. Reading that one pattern lets you check a layout for phones without opening one."}]},{"t":"h2","id":"tailwind-since-you-will-meet-it","runs":[{"text":"Tailwind, since you will meet it"}]},{"t":"p","runs":[{"text":"Generated interfaces are usually Tailwind, which puts the styling in the class attribute:"}]},{"t":"code","lang":"html","label":"HTML","text":"<div class=\"flex flex-col gap-4 p-6 md:flex-row md:items-center\">"},{"t":"p","runs":[{"text":"Flex container, stacked vertically, 1rem between children, 1.5rem of padding, becoming a centred row at 768 pixels and above. It looks noisy and it has one real advantage for this course: the styling is next to the element, so you can read a component’s appearance without opening a second file and without wondering which rule wins."}]},{"t":"h2","id":"the-five-minute-screen-audit","runs":[{"text":"The five-minute screen audit"}]},{"t":"p","runs":[{"text":"For any screen a model just built:"}]},{"t":"ol","start":1,"items":[[{"text":"Narrow the browser window to 360 pixels. Does anything scroll sideways, overlap, or fall off?"}],[{"text":"Press Tab repeatedly. Does focus move through every control in a sensible order, and can you see where it is?"}],[{"text":"Submit the form with the keyboard only. Enter should work."}],[{"text":"Check that every input has a visible label, not just placeholder text. Placeholders vanish when typing starts and are invisible to some assistive technology."}],[{"text":"Zoom the browser to 200 percent. Text should reflow, not get cut off."}]]},{"t":"p","runs":[{"text":"Five minutes, no tools installed, and it catches the majority of what makes a generated interface unusable for somebody who is not you on your own laptop."}]},{"t":"h2","id":"free-ways-to-go-further","runs":[{"text":"Free ways to go further"}]},{"t":"p","runs":[{"text":"MDN Web Docs is the reference professionals actually use and it is free. The browser’s own element inspector, covered two lessons from now, shows you which CSS rule is producing any given pixel. Between those two you can answer nearly every layout question without asking anybody."}]}],"check":{"question":[{"text":"A generated form saves bookings with an empty customer name, though the field is filled in on screen. What is the most likely cause?"}],"options":[{"text":[{"text":"The "},{"text":"required","code":true},{"text":" attribute is missing, so the browser allowed an empty submission."}],"correct":false,"tempting":[{"text":"Would prevent an empty field being submitted, but the field was not empty; the value was lost between the form and the server."}]},{"text":[{"text":"The input’s "},{"text":"name","code":true},{"text":" attribute does not match the key the server reads from the request."}],"correct":true,"tempting":null},{"text":[{"text":"The label is not associated with the input, so the value was not captured."}],"correct":false,"tempting":[{"text":"Confuses an accessibility association with data binding; labels affect announcement and focus, never the submitted value."}]},{"text":[{"text":"The database column is too short and truncated the value to nothing."}],"correct":false,"tempting":[{"text":"Truncation shortens a value rather than emptying it, and would raise an error in most databases rather than silently storing blank."}]}],"order":[0,2,1,3],"why":null},"prev":{"slug":"the-shape-of-a-project","title":"The shape of a project you did not lay out","minutes":8},"next":{"slug":"enough-javascript-to-review","title":"Enough JavaScript to review a change","minutes":9}}}