HTML & CSS
HTML and CSS are the two languages every web page is built from: HTML says what each part of the page is, and CSS says how it looks and where it sits. Both are usually treated as the easy layer, and both run on rules precise enough that almost every “why does it look like that” has an exact answer. The page opens with twelve symptoms of a page that does not do what its code says, each with the rule behind it and the fix, then covers what the browser does with the two files, the promises HTML makes, the cascade, the box and the layout, what costs the user, and five documented cases.
- Specified by
- WHATWG · W3C
- Layout tools
- Flow · Flex · Grid
- Home pages failing WCAG
- 95.9% · WebAIM 2026
Twelve symptoms and the rule behind each
Here is the table first, because it is the reason most people open a page about HTML and CSS: something on the screen is not doing what the code says. Each row is a symptom you can see, the rule that produces it, and the fix. None of the twelve is a browser bug. Each one is a rule that was always there, meeting code written by someone who had not met it yet — and there are few enough rules that the rest of this page covers all of them.
| Symptom | What is actually happening | The fix |
|---|---|---|
| 1. My style is ignored | It lost at one of the cascade’s four gates, or the selector never matched the element. | Find the gate you lost at (the cascade, below) before raising specificity. |
2. z-index: 9999 does nothing | The element sits inside a stacking context that is itself painted lower; the number only competes inside it. | Find the ancestor that creates the context — often a transform or an opacity — and fix the order there. |
3. width: 100% plus padding overflows | By default the width covers the content only; padding and border are added on top of it. | box-sizing: border-box on everything — almost every CSS reset does it. |
| 4. The gap between two blocks is smaller than both margins added up | Vertical margins between blocks in normal flow collapse into the larger one. It is in the spec. | Space children with gap in a flex or grid parent, or with padding; neither collapses. |
| 5. A long word or link bursts out of its card | A flex item will not shrink below its longest unbreakable word: its minimum width defaults to its content. | min-width: 0 on the item, overflow-wrap: anywhere on the text. |
| 6. The page scrolls sideways | Something is wider than the screen: a fixed width, 100vw (which includes the scrollbar), or an unbroken string. | Find it by outlining every element for a minute, then switch to max-width and relative units. |
7. position: sticky does not stick | An ancestor has overflow set and has become the scroll box, the parent is no taller than the element, or there is no top. | Remove the ancestor’s overflow, give the parent room to scroll past, set a top. |
| 8. An absolutely positioned element flies to the corner of the page | It is placed against the nearest positioned ancestor, and there is none, so it uses the page. | position: relative on the element it should be pinned to. |
| 9. The page jumps while it loads | Content arrived with no space reserved for it: an image without dimensions, a late font, a banner inserted above. | width and height, or aspect-ratio, on media; reserved space for anything injected later. |
| 10. A full-screen block is cut off at the bottom on a phone | On mobile 100vh is the height with the browser bar hidden, so the block is taller than what is visible. | 100svh for a stable height, 100dvh to follow the bar as it moves. |
| 11. An animation stutters | It animates width, top or margin, which reruns layout and paint on every frame. | Animate transform and opacity, which only the last step of the pipeline has to redo. |
| 12. A button works with a mouse and nothing else | It is a <div> with a click handler: no focus, no keyboard, nothing for a screen reader to announce. | A <button>. The section on HTML below lists what it gives you for free. |
What the browser does with two files
HTML and CSS look like documents, so they get read like documents — top to bottom, as a description of the page. The browser reads them as input to a program it reruns every time anything on the page changes. Knowing the steps of that program is what turns the table above from twelve facts to memorise into one mechanism to understand.
First comes parsing. The HTML becomes the DOM — a tree of objects, one per element, that scripts can read and change. The CSS becomes the CSSOM, the same rules turned into objects the browser can query. Then four steps run in order. Style walks the DOM and, for every element and every property, runs the cascade to pick one value. Layout takes those values and works out the size and position of every box. Paint fills in the pixels, layer by layer, in stacking order. Composite assembles the painted layers on the screen, usually on the GPUGraphics Processing Unit — a processor for parallel mathsA processor built from thousands of simple cores that apply the same operation to large arrays of numbers at once. That shape happens to be exactly what training and running models needs, which is why GPUs long ago stopped being only about graphics.All terms.
The branch on the right of the diagram is the one most people never hear about. Alongside the DOM, the browser builds an accessibility tree, in which every node has a role, a name, a state and the actions it supports. Screen readers, voice control and the operating system’s accessibility layer read that tree, and it is built from the HTML alone. No CSS reaches it. A <div> styled to look exactly like a button is, in that tree, a piece of text.
| Step | What it decides | Rows of the table it explains |
|---|---|---|
| Style | Which value each property of each element gets | 1 |
| Layout | The size and position of every box | 3–10 |
| Paint | The pixels, and what is drawn over what | 2 |
| Composite | How the painted layers reach the screen | 11, and its fix |
| Accessibility tree | What assistive technology can see and do | 12 |
HTML is a set of promises
A <button> is not a box that looks like a button. It is focusable with the keyboard, it responds to Enter and Space, it announces itself to a screen reader as a button, it participates in form submission, and it is exposed to browser automation and to the operating system’s accessibility layer. A <div> with a click handler has none of that, and reproducing all of it correctly takes about forty lines that you will get subtly wrong.
The diagram is the whole argument in one frame. Semantic elements put the right role, name and state into the accessibility tree without a line of code; a <div> puts in nothing, and every missing piece has to be added back with ARIAAccessible Rich Internet Applications — accessibility attributesA set of HTML attributes — role, aria-label, aria-expanded and the rest — that tell assistive technology what an element is and what state it is in. It only describes; it implements nothing: role="button" makes a screen reader announce a button, and the element still ignores the keyboard until you write that code yourself.All terms attributes and JavaScript. Most of the time the choice is not between two ways of building a button. It is between using the one the browser already built and rebuilding it worse.
The button is the famous case, and it is one of several. Each element below does work that teams routinely rebuild by hand, usually because the native one was not styled the way the design wanted and restyling it looked harder than replacing it.
| Element | What you get for free | What gets built instead |
|---|---|---|
| <button> | Focus, Enter and Space, a role, form submission, a working disabled | A <div> with a click handler |
| <a href> | A real link: open in a new tab, copy the address, followed by crawlers | A button or span that changes the page from script |
| <label for> | A name for the input, and a bigger target to click | Placeholder text |
| <dialog> | Opened with showModal(): focus moved inside, Escape closes it, the rest of the page inert | A <div> with a high z-index |
| <details> | Opens and closes from the keyboard, with no JavaScript at all | A hand-rolled accordion |
| <nav>, <main> | Landmarks a screen reader can jump between | Divs with class names |
Two habits carry most of the practical benefit. Headings must describe the structure of the document rather than its type size — many people navigate a page by jumping between headings, and a page whose only <h1> is the logo gives them nothing. And every form control needs an associated <label>; a placeholder is not a label, because it disappears exactly when the user needs it and is frequently not announced at all. One attribute belongs beside them: the <html> element needs lang, because it is how a screen reader picks the voice and the pronunciation — and 13.5% of the home pages of the million most popular websites still leave it out.
The cascade is an algorithm
When two declarations set the same property on the same element, the browser does not guess. It runs four comparisons in a fixed order and stops at the first one that separates them. Almost every “why is my style not applying” question — row 1 of the table — is answered by knowing which comparison you are actually losing at.
| Gate | What is compared | Where people lose |
|---|---|---|
| 1. Origin and importance | Author, user and browser styles, with !important inverting the order between them. | An !important in a vendor stylesheet you did not write. |
| 2. Cascade layers | Which @layer a rule belongs to; layers are compared in the order they were declared. | A very specific selector inside an early layer still losing to a plain one in a later layer. |
| 3. Specificity | Ids, then classes and attributes and pseudo-classes, then elements — compared as three columns, not summed. | Assuming eleven classes beat one id. They do not — the id column is checked first. |
| 4. Order of appearance | The last matching declaration in source order wins. | Bundler output ordering your files differently from what you expected. |
Cascade layers are worth adopting for one reason above the rest: they let you separate “how strongly should this win” from “how precisely does this select”. Put a third-party stylesheet in an early layer and your own overrides in a later one, and you can stop escalating selectors entirely — a single class in a later layer beats an id in an earlier one, because gate two is decided before gate three. Layers have worked in every major browser since early 2022, so the reason not to use them is gone.
Two things get mistaken for the cascade. The first is inheritance: a few properties — color, font-family and line-height among them — pass from parent to child when nothing sets them, while most — margin, border, width — never do. An element showing the wrong colour with no rule of its own is not losing the cascade; it has nothing in it, and it is inheriting. The second is the pair of selectors that change specificity on purpose. :where() wraps a selector and gives it zero specificity, which is how a library ships defaults you can override with a single class. :is() takes the specificity of its most specific argument, which is how a harmless-looking selector suddenly wins.
The box, then the layout
Every element the browser lays out is a rectangle made of four layers: the content, the padding around it, the border, and the margin outside the border. Two defaults in that model produce two rows of the table. First, width sets the width of the content alone, so padding and border are added on top — which is row 3, and why almost every stylesheet starts with box-sizing: border-box, which makes width mean the whole box. Second, vertical margins between blocks in normal flow collapse: 24 pixels below one paragraph and 16 above the next produce a gap of 24, not 40. That is row 4. It is deliberate — it keeps running text evenly spaced — and it happens only in normal flow, never between flex or grid items.
Before any of the layout systems, there is normal flow — the behaviour a document has with no CSS at all. Block elements stack down the page and fill the available width; inline elements sit along a line and wrap. A great deal of layout code exists to recreate something normal flow already did, and the cheapest layout is the one you do not write.
Flexbox — one axis
- Content decides the sizes; the container distributes space
- A row or a column, wrapping if you allow it
- Right for toolbars, button rows, card internals
- Alignment along and across the one axis
Grid — two axes
- The container decides the structure; content fills it
- Rows and columns defined up front, by name if you like
- Right for page structure and any real two-dimensional layout
- Items can be placed and can overlap deliberately
The decision rule is short: if you are arranging things along a single line and the content should decide the sizes, use flex; if you are defining a structure that content drops into, use grid. They compose freely — a grid page with flex inside each cell is the ordinary case, and arguing about which is “better” is arguing about whether a hammer beats a saw.
One flex default is worth knowing by name, because it produces row 5. The minimum width of a flex item is its content, so the item refuses to shrink below its longest unbreakable word or address and pushes out of its container instead. min-width: 0 lets it shrink. Grid has the same trap in a different spelling, and minmax(0, 1fr) is the same fix.
Positioning and stacking
Positioning takes an element out of the ordinary layout, and every variant is defined against something. relative shifts the element from where it would have been and leaves its space behind. absolute places it against the nearest ancestor that is itself positioned — and when there is none, against the page, which is row 8. fixed places it against the viewportthe visible part of the pageThe rectangle of the browser window the page is actually drawn in — the screen minus the browser’s own bars. On a phone it changes size as the address bar slides away, which is why a height of 100vh can be taller than what the reader sees.All terms. sticky behaves normally until its scroll container would carry it out of view, then holds it in place — and the scroll container is the nearest ancestor with overflow set, which is why a stray overflow: hidden three levels up is the usual cause of row 7.
Then there is what draws over what. z-index is not a global ranking. It orders elements within a stacking context, and a new context gets created quietly — by a positioned element with a z-index, but also by opacity below 1, any transform, a filter or isolation: isolate. Everything inside a context is painted together, as one unit, at its parent’s place in the order.
What modern CSS removed
A large amount of received wisdom in this area is advice for problems that no longer exist. If you learned CSS from material written before roughly 2022, several of these will be new, and each one deletes a workaround rather than adding a technique.
- Custom properties (
--gap) are real values the browser resolves at runtime, so they cascade, respond to media queries and can be read and set from JavaScript — unlike preprocessor variables, which are gone by the time the browser sees the file. - Nesting rules inside rules works in plain CSS, in every major browser since 2023 — one of the last reasons a small project needed a preprocessor at all.
clamp()replaces most breakpoint-based type scales: one declaration gives a minimum, a fluid middle and a maximum.- Container queries let a component respond to the width of its own container rather than the viewport, which is what a reusable component actually needed all along.
:has()selects a parent by what it contains — the “parent selector” that was declared impossible for two decades.aspect-ratioreserves a box’s shape before its content arrives, which retires the padding-percentage hack and prevents row 9 for every video embed.- The viewport units
svh,lvhanddvhdescribe a phone screen with the browser bar shown, hidden, or as it is right now — the fix for row 10. - Logical properties (
margin-inline,padding-block) describe direction relative to the writing mode, so a layout works in a right-to-left language without a mirrored stylesheet. gapworks in flexbox and grid alike, which removes the last reason to put margins on children and then strip the first or last one.
What actually costs the user
CSS is render-blocking by design: the browser will not paint until it has the stylesheets for the current media, because painting first and restyling second would flash unstyled content at everyone. That is why the size and the number of stylesheets in the document head is a first-paint problem rather than a bandwidth one.
The bars under the pipeline diagram are the other half of the story. Changing a property restarts the pipeline from the first step that property affects, and during an animation that restart happens sixty times a second or more:
| You change | Steps that rerun | Cost per animation frame |
|---|---|---|
width, height, top, margin, font-size | Style, layout, paint, composite | Expensive — every box around it may move |
color, background, box-shadow | Style, paint, composite | Moderate — nothing moves, pixels are redrawn |
transform, opacity | Style, composite | Cheap — the painted layer is moved or faded as it is |
That is the whole explanation of row 11. transform and opacity can be handled by the compositor alone, as long as the element sits on its own layer — which browsers arrange for anything being animated — so an animation built on them stays smooth even while the main thread is busy with something else. The same slide-in written with left runs layout for the element and everything around it on every frame.
Two visible failures are worth naming because both are cheap to prevent. Layout shift — row 9 — happens when content arrives without space reserved for it: an image with no dimensions, a font swapping to a different metric, an ad slot appearing above what someone is reading. Google measures it as CLS, one of the three Core Web VitalsGoogle’s page-experience metricsThree measurements Google takes from real visitors’ browsers: LCP, how fast the main content appears; INP, how fast the page responds to input; and CLS, how much the layout jumps. They feed into search ranking, but only as one signal among many — a fast page with nothing worth reading still loses to a slow one that answers the question.All terms it has used in ranking since 2021, and treats a score above 0.1 as needing improvement. Reserve the space and it does not happen. Layout thrash happens when JavaScript reads a geometric property, writes a style, then reads again in a loop, forcing the browser to recalculate layout synchronously on every iteration; batch the reads, then the writes.
Five cases, and what each one was missing
Every case below is documented in public — by a court, a regulator or the company itself. In none of them was the missing piece advanced. It was an element, an attribute or a habit this page has already named.
| Case | What happened | What was missing |
|---|---|---|
| Target, 2006–2008 | A class action by blind shoppers who could not use Target.com, settled with a $6M fund | Text alternatives for images; a checkout usable without a mouse |
| Domino’s, 2016–2019 | A blind customer could not order with a screen reader; the US Supreme Court declined to hear Domino’s appeal | Controls a screen reader can name and operate |
| WebAIM Million, 2026 | 95.9% of a million home pages fail automated WCAG checks; 96% of the errors fall into six categories | Contrast, alt, labels, link and button text, lang |
| Facebook, 2020 | Over 400 KB of compressed CSS on the homepage, about a tenth of it used for the first screen | A way to delete CSS safely: locality |
Mobile 100vh, until 2022 | Full-screen layouts cut off under the browser bar on every phone | Units for the real visible height: svh, dvh |
Domino’s is the same failure a decade later, with the question of law settled. Guillermo Robles, who is blind, could not order a pizza through the website or the app with a screen reader, and sued in 2016. In January 2019 the Ninth Circuit ruled that the Americans with Disabilities Act (ADA) covers a business’s website and app when they are connected to its physical stores, and in October 2019 the US Supreme Court declined to hear Domino’s appeal. Since then an inaccessible checkout in the United States has been a legal exposure, not a backlog item. The WebAIM Million shows how far practice is from that: its February 2026 scan of a million home pages found WCAGWeb Content Accessibility Guidelines — the accessibility standardThe W3C’s standard for making web content usable by people with disabilities, written as testable success criteria at three levels, A, AA and AAA. Level AA is what laws and contracts usually point at, and meeting it on paper is not the same as a blind person being able to finish your checkout.All terms failures on 95.9% of them, and 96% of all the errors fell into six categories — low-contrast text, missing alt, unlabelled form fields, empty links, empty buttons and a missing lang. Every one of the six is a default this page has already described.
You do not need their tooling to buy the same property. The thing that makes a stylesheet unmaintainable is almost never its size. It is that nobody can predict what deleting a rule will break, which happens when selectors reach across component boundaries — a rule in the header file that styles something in the footer because both matched .card h3. Whatever methodology you use to prevent that (a naming convention, CSS modules, scoped styles in a framework, or layers plus discipline), the property you are buying is the same one: locality, so a change stays where you made it.
The last row is a different kind of case: a platform decision rather than a team’s mistake. Mobile browsers defined 100vh as the height with their address bar hidden, so for years every full-screen hero was taller than the screen it loaded on, and the workarounds were scripts that measured the window. CSS fixed it at the source with three units — svh, lvh, dvh — which reached Safari in March 2022 and Chrome in November 2022. If a codebase still sets a height from window.innerHeight, it is carrying a workaround for a problem that no longer exists.
Where it degrades
HTML and CSS rarely fail loudly. A page with a dozen of the problems above still renders, still passes review on a designer’s laptop, and still looks finished. These are the shapes the decay takes.
- Divs with click handlers instead of buttons, which are invisible to the keyboard and to every assistive technology.
outline: noneon focus with nothing put in its place, which makes keyboard navigation impossible to follow.- Headings chosen for their size, which destroys the document outline people navigate by.
- An
!importantarms race, and its twin, thez-indexarms race — each side can only reply with a bigger version of the same move. - Selectors reaching across component boundaries, so nobody can delete a rule safely.
- Magic numbers —
margin-top: 37px— which encode one screen size and break on the next. - Images without dimensions, which move the text under a reader’s finger as the page loads.
- An accessibility overlay — one script that promises to fix the page — in place of fixing the markup. In 2025 the US Federal Trade Commission made accessiBe pay $1 million for claiming its widget could make any website WCAG-compliant.
- A perfect automated accessibility score treated as the finish line rather than the first check.
There is also an honest limit, and it matters more than any row in the table. Everything on this page makes the browser do what you meant. None of it tells you whether what you meant can be used. Automated checkers see part of that at best: when the UK Government Digital Service built a test page with 143 deliberate barriers in 2017, the best of ten tools found 41% of them, all ten together found 71%, and 29% were found by none — links marked only by colour among them, which pass every rule a machine can check.
The response is cheap and has no substitute. Put the mouse away and use the page with the keyboard for five minutes. Turn on the screen reader your operating system already has — VoiceOver on a Mac or an iPhone, Narrator on Windows, TalkBack on Android — and try to complete the one task the page exists for. Load it on a real mid-range phone over a slow connection. Ten minutes of that finds what no linter will, and it is the only check on this page that tests the promise rather than the code.
When to use it
Use it when
- Always — every framework on this list compiles down to these two languages.
- Reach for plain HTML and CSS first for content pages, where a framework adds cost and no capability.
- Use native elements before ARIA, and ARIA only to describe what you have genuinely implemented.
- Use cascade layers when integrating third-party styles you cannot edit.
Avoid it when
- Hand-rolling widgets that already exist natively — a dialog, a details disclosure, a date input.
- Adding a CSS methodology to a project small enough that locality is not yet a problem.
- Reproducing a layout system in JavaScript because a resize listener felt easier than grid.
- An accessibility overlay in place of fixing the markup — it cannot repair what the HTML never promised.
- Optimising selector performance, which has not been a real bottleneck for over a decade.
Sources and method
What this page stands on
- 13 sources
- 3 documented failures
- Checked October 13, 2026
Practices and where the terms come from
the rules behind the twelve symptoms- StandardHTML Living Standard (opens in a new tab)WHATWG
- StandardCSS Cascading and Inheritance Level 5 (opens in a new tab)W3C · Jan 13, 2022
- StandardCSS Positioned Layout Module Level 3 (opens in a new tab)W3C · Oct 7, 2025
- StandardCSS Values and Units Module Level 4 (opens in a new tab)W3C · Mar 12, 2024
- GuideUsing ARIA (opens in a new tab)W3C · Feb 24, 2026
- DocumentationMDN — Introduction to the CSS cascade (opens in a new tab)MDN Web Docs
- DocumentationMDN — Stacking context (opens in a new tab)MDN Web Docs
The failures this page learns from
the lawsuits and the order in “Five cases, and what each one was missing”- ArticleNational Federation of the Blind v. Target Corp. (opens in a new tab)Wikipedia
- Regulator reportRobles v. Domino’s Pizza, LLC — opinion (opens in a new tab)U.S. Court of Appeals for the Ninth Circuit · Jan 15, 2019
- Regulator reportFTC Order Requires Online Marketer to Pay $1 Million for Deceptive Claims that its AI Product Could Make Websites Compliant with Accessibility Guidelines (opens in a new tab)Federal Trade Commission · Jan 2025
Research
how much of the web fails, what tools find, and what CSS costs- ResearchThe WebAIM Million — the 2026 report on the accessibility of the top 1,000,000 home pages (opens in a new tab)WebAIM · Feb 2026
- ResearchWhat we found when we tested tools on the world’s least-accessible webpage (opens in a new tab)GOV.UK Accessibility blog · Feb 24, 2017
- ArticleRebuilding our tech stack for the new Facebook.com (opens in a new tab)Engineering at Meta · May 8, 2020
Researched with Claude. Finding and cross-checking the primary sources was done with Claude (Anthropic). Selection, writing and fact-checking: the Anatomy of IT Product editors.
Found this useful?
Share it with someone who is working on the same problem.