A beginner-friendly guide to every common HTML tag and CSS property,
plus how to use them well. Not sure where to start? Open
Style by Element in the sidebar, pick the thing you
are working with (text, a list, a link, an image…), and see
exactly which tags and CSS properties apply.
How this guide is organised
Section
Use it when…
Style by Element
You know what you want to style (a heading, a table, a
button) but not how. Start here.
All HTML Elements
You want the full catalogue of tags grouped by purpose, with a
one-line definition and example.
All CSS Properties
You want the full catalogue of properties grouped by what they
affect (text, box model, layout, animation…).
CSS Selectors & Cascade
You need to target the right element — classes, IDs,
combinators, pseudo-classes, specificity.
Units, Colors & Functions
You are unsure whether to use px, rem,
%, fr, or how to write a color.
Live Editor
Type HTML + CSS and see it render instantly. Load a preset.
Golden Rules of Web Development
1. Semantic HTML over "div soup"
A common beginner mistake is using <div> for
everything. <div> works visually, but screen readers
and search engines rely on meaningful tags (<header>,
<nav>, <main>,
<article>, <button>) to
understand the page. Reach for a <div> only when no
semantic element fits and you just need a styling wrapper.
2. Separation of concerns
HTML is structure. CSS is presentation. JavaScript is behaviour. Keep
CSS in a stylesheet (or one <style> block), not in
style="…" attributes scattered through the markup.
Style with classes so the same rule can be reused.
3. Accessibility is not optional
Every image needs alt text. Every form field needs a
<label>. Keyboard focus must stay visible. Text
needs enough contrast against its background (aim for a 4.5:1 ratio).
Use headings <h1>–<h6> in
order without skipping levels.
4. Build mobile-first and responsive
Include <meta name="viewport" content="width=device-width,
initial-scale=1">. Prefer relative units (rem, %,
fr, ch) over fixed px for
layout. Let content reflow; add @media queries to adjust
at larger sizes.
5. Keep CSS maintainable
Set box-sizing: border-box once, globally, so padding
and borders don't inflate widths.
Store repeated values (colors, spacing) in
custom properties:
--brand: #2563eb; then color: var(--brand);
Avoid !important. If you need it, your selectors are
probably fighting each other.
Use gap for spacing between flex/grid items instead of
margins on each child.
Things to avoid
Don't use <br> to create space between
paragraphs — use CSS margin.
Don't use deprecated tags: <center>,
<font>, <big>,
<marquee>, <blink>,
<strike>, <tt>,
<frame>. They are obsolete.
Don't make a <div> or <span>
clickable when you mean a <button> or
<a>.
Don't remove focus outlines (outline: none) without
providing a visible replacement.
Don't rely on color alone to convey meaning (e.g. red = error).
Add text or an icon.
Style by Element
Pick what you are working with. You'll get the HTML tags involved,
every CSS property that meaningfully styles it, best practices, and a
runnable example you can open in the Live Editor.
All HTML Elements
Grouped by purpose. "Void" elements have no closing tag and no
content (e.g. <img>, <br>).
Document & Metadata
Tag
Definition
Example
<!DOCTYPE html>
First line of every page; tells the browser to use standards mode.
<!DOCTYPE html>
<html>
Root element. Put lang on it for accessibility.
<html lang="en">
<head>
Container for metadata: title, links, scripts, styles. Not visible.
<head>…</head>
<body>
Contains everything visible on the page.
<body>…</body>
<title>
Text shown in the browser tab and search results.
<title>My Site</title>
<meta>
Metadata: charset, viewport, description, social cards. Void.
<meta name="description" content="…">
<link>
Links an external resource, most often a stylesheet or favicon. Void.
<link rel="stylesheet" href="style.css">
<style>
Internal CSS.
<style>body{margin:0}</style>
<base>
Sets a base URL/target for all relative links on the page. Void.
<base href="/app/">
<script>
Runs or links JavaScript. Add defer to load without blocking.
<script src="app.js" defer></script>
<noscript>
Content shown only when JavaScript is disabled.
<noscript>Enable JS</noscript>
Sectioning & Semantic Structure
Tag
Definition
Example
<header>
Introductory content for the page or a section (logo, site nav, section heading).
<header>…</header>
<nav>
A block of major navigation links.
<nav>…</nav>
<main>
The unique primary content. Exactly one per page, not nested in article/aside.
<main>…</main>
<section>
A thematic grouping, normally with its own heading.
<section>…</section>
<article>
Self-contained content that would still make sense on its own (post, card, comment).
<article>…</article>
<aside>
Content tangential to the main content (sidebar, pull quote, related links).
<aside>…</aside>
<footer>
Closing content for a page or section (credits, links, copyright).
<footer>…</footer>
<h1>–<h6>
Headings, most to least important. One <h1> per page; never skip levels.
<h2>Section title</h2>
<hgroup>
Groups a heading with an adjacent subheading/tagline.
<hgroup><h1>…</h1><p>…</p></hgroup>
<address>
Contact information for the nearest article/page.
<address>hi@site.com</address>
<search>
Wraps a search/filter form (newer element).
<search><form>…</form></search>
Grouping & Block Text
Tag
Definition
Example
<p>
A paragraph of text.
<p>Hello.</p>
<div>
Generic block container with no meaning. Use only for styling/layout grouping.
<div class="row">…</div>
<hr>
A thematic break between paragraph-level content. Void.
<hr>
<pre>
Preformatted text; whitespace and line breaks are preserved. Monospaced.
<pre> kept spacing</pre>
<blockquote>
A longer quotation from another source. cite attribute holds the URL.
<blockquote cite="…">…</blockquote>
<figure>
Self-contained media (image, diagram, code) referenced from the main text.
<figure><img…><figcaption>…</figcaption></figure>
<figcaption>
Caption for its parent <figure>.
<figcaption>Fig 1</figcaption>
<ul> <ol> <li>
Unordered list, ordered list, and list item. See the Lists section.
<ul><li>A</li></ul>
<dl> <dt> <dd>
Description list: wrapper, term, and its description. Good for key/value pairs.
<dl><dt>HTML</dt><dd>markup</dd></dl>
<menu>
A semantic unordered list of commands/toolbar items (behaves like <ul>).
<menu><li><button>Copy</button></li></menu>
Inline Text Semantics
Meaning vs. looks.<strong> and
<em> carry importance and
emphasis (announced by screen readers).
<b> and <i> are purely visual
(bold / italic) with no added meaning. Prefer the semantic pair.
Tag
Definition
Example
<a>
Hyperlink. href is the destination. For links that open a new tab add rel="noopener".
<a href="/about">About</a>
<span>
Generic inline container with no meaning; a hook for styling part of a line.
<span class="hl">word</span>
<strong>
Strong importance / seriousness / urgency. Bold by default.
<strong>Warning</strong>
<em>
Stress emphasis that changes sentence meaning. Italic by default.
<em>really</em>
<b>
Draw attention without extra importance (keywords, product names). Bold.
Groups related controls with a visible caption (e.g. a set of radios).
<fieldset><legend>Size</legend>…</fieldset>
<output>
The result of a calculation or user action.
<output name="sum">0</output>
<progress>
Completion of a task (a loading/progress bar).
<progress value="0.7"></progress>
<meter>
A scalar measurement within a known range (disk usage, score).
<meter value="6" max="10"></meter>
Interactive Elements
Tag
Definition
Example
<details>
A disclosure widget the user can open/close. No JavaScript needed.
<details><summary>More</summary>…</details>
<summary>
The always-visible label/heading for its <details>.
<summary>FAQ</summary>
<dialog>
A modal or non-modal dialog box. Open with .showModal().
<dialog open>Hi</dialog>
Scripting & Templates
Tag
Definition
Example
<template>
Inert markup that is not rendered; cloned by JavaScript to build UI.
<template id="row">…</template>
<slot>
A placeholder inside a Web Component for projected content.
<slot name="title"></slot>
Global Attributes (work on almost every tag)
Attribute
Purpose
id
Unique name for one element; target for CSS #id, JS, and <label for>.
class
One or more reusable names for CSS/JS. The main styling hook.
style
Inline CSS. Avoid — use classes instead.
title
Advisory tooltip text on hover.
lang / dir
Language and text direction (ltr/rtl).
hidden
Hides the element (like display:none) until removed.
tabindex
0 makes a non-focusable element focusable; -1 only via script. Avoid positive values.
data-*
Custom data you define, e.g. data-user-id="7"; read in JS via dataset.
role, aria-*
Accessibility hints for assistive tech when semantics aren't enough.
contenteditable
Makes the element's text editable in place.
All CSS Properties
Grouped by what they affect. Many are shorthands (e.g.
font, border, background,
flex, grid, transition,
animation) that set several longhand properties at once.
Typography & Text
Property
Definition
Example
color
Text (foreground) color.
color: #1e293b;
font-family
Ordered list of fonts; end with a generic (sans-serif).
font-family: "Inter", system-ui, sans-serif;
font-size
Text size. Prefer rem so it respects user settings.
font-size: 1.125rem;
font-weight
Thickness: 100–900, or normal/bold.
font-weight: 600;
font-style
normal, italic, or oblique.
font-style: italic;
font-variant
Alternate glyphs, e.g. small-caps.
font-variant: small-caps;
font
Shorthand: style, weight, size/line-height, family (in that order).
font: italic 600 1rem/1.5 sans-serif;
line-height
Vertical space per line. Unitless 1.4–1.7 for body text.
line-height: 1.6;
letter-spacing
Space between characters (tracking).
letter-spacing: 0.02em;
word-spacing
Extra space between words.
word-spacing: 4px;
text-align
left, right, center, justify.
text-align: center;
text-indent
Indent of the first line of a block.
text-indent: 2em;
text-decoration
Underline/overline/line-through + its color, style, thickness.
text-decoration: underline dotted 2px;
text-transform
uppercase, lowercase, capitalize.
text-transform: uppercase;
text-shadow
x, y, blur, color.
text-shadow: 0 1px 2px rgba(0,0,0,.4);
text-overflow
How clipped text ends; use ellipsis with overflow:hidden;white-space:nowrap.
text-overflow: ellipsis;
white-space
How whitespace/wrapping behave: normal, nowrap, pre, pre-wrap.
white-space: nowrap;
word-break / overflow-wrap
Break long unbreakable strings (URLs) so they don't overflow.
overflow-wrap: anywhere;
hyphens
Allow automatic hyphenation (needs lang).
hyphens: auto;
text-wrap
balance evens out short headings; pretty avoids orphans.
text-wrap: balance;
writing-mode / direction
Vertical text; right-to-left flow.
direction: rtl;
vertical-align
Vertical alignment of inline/inline-block/table-cell content.
Blend an element (or its layers) with what's behind, like Photoshop modes.
mix-blend-mode: multiply;
accent-color
Brand color for checkboxes, radios, range, progress.
accent-color: #2563eb;
caret-color
Color of the text-input blinking cursor.
caret-color: #2563eb;
Borders & Outline
Property
Definition
Example
border
Shorthand: width, style, color. Style is required to see it.
border: 1px solid #cbd5e1;
border-width / -style / -color
Individual pieces; each accepts up to 4 side values.
border-style: dashed;
border-top / -right / -bottom / -left
Style one side only.
border-bottom: 2px solid #000;
border-radius
Rounds corners. 50% on a square = circle.
border-radius: 8px;
outline
Like a border but doesn't take up space; used for focus rings. Don't remove without a replacement.
outline: 2px solid #2563eb;
outline-offset
Gap between the outline and the border edge.
outline-offset: 2px;
box-shadow
x, y, blur, spread, color. inset for inner shadow. Comma-separate for layers.
box-shadow: 0 4px 12px rgba(0,0,0,.1);
Box Model: Sizing & Spacing
Margin vs. padding.margin is space
outside the border that pushes neighbours away.
padding is space inside the border between the
edge and the content. Set box-sizing: border-box so
width includes padding and border.
Property
Definition
Example
box-sizing
border-box = width/height include padding+border. Set it globally.
box-sizing: border-box;
width / height
Explicit size. Often better to let content decide.
width: 320px;
min-width / max-width
Lower/upper bound. max-width is key for responsive containers.
max-width: 65ch;
min-height / max-height
Lower/upper bound on height.
min-height: 100vh;
aspect-ratio
Keeps a width:height ratio as the element scales.
aspect-ratio: 16 / 9;
margin
Outer space. 1–4 values (all / y x / top x bottom / T R B L). auto centers a fixed-width block.
How much an item grows, shrinks, and its starting size.
flex-grow: 2;
order
Reorder items visually without changing the HTML.
order: -1;
CSS Grid (2-D layout)
Flexbox vs. Grid. Use Flexbox for
one direction — a row of buttons, a nav bar, centering one thing.
Use Grid when you control rows and columns
— a gallery, a dashboard, a page shell.
Property
Definition
Example
display: grid
Makes a grid container.
display: grid;
grid-template-columns
Column tracks. fr = fraction of free space; repeat() and minmax() help.
grid-template-columns: repeat(3, 1fr);
grid-template-rows
Row tracks.
grid-template-rows: auto 1fr auto;
grid-template-areas
Name regions and place items by name.
grid-template-areas: "head head" "nav main";
grid-auto-flow / grid-auto-rows
How and how big implicitly-created tracks are.
grid-auto-rows: 120px;
gap
Gutter between rows and columns.
gap: 20px;
grid-column / grid-row
Which lines an item spans.
grid-column: 1 / 3;
grid-area
Place an item into a named area (or all four lines at once).
grid-area: main;
justify-items / align-items / place-items
Align items inside their cells (inline / block / both).
place-items: center;
justify-content / align-content
Align the whole grid within the container when it's smaller.
Pull in another stylesheet. Slower than <link> — avoid for performance.
@import url("base.css");
@layer
Group rules into ordered cascade layers to tame specificity.
@layer base, components, utilities;
All JavaScript Basics
HTML is structure, CSS is presentation, and JavaScript is
behaviour — it's what makes a page react to
clicks, validate a form, or update itself without reloading. This
covers the core language and the DOM APIs you'll use every day.
Where your JavaScript goes
Way
Definition
Example
External file
Preferred. Keeps JS out of your markup, and the browser can cache it.
<script src="app.js" defer></script>
Inline block
JS written directly in the page. Fine for small demos.
<script>console.log("hi");</script>
defer
Downloads in the background, runs after the HTML is parsed, in order. Use this for most scripts.
<script src="a.js" defer></script>
async
Downloads in the background, runs as soon as it's ready (order not guaranteed). Good for independent, analytics-style scripts.
<script src="a.js" async></script>
No attribute, in <head>
Blocks HTML parsing until it downloads and runs. Avoid — prefer defer or place before </body>.
<script src="a.js"></script>
Variables & data types
Keyword / type
Definition
Example
let
A variable that can be reassigned later. Default choice for values that change.
let count = 0; count = count + 1;
const
A variable that can't be reassigned. Prefer this unless you know the value will change.
const name = "Ada";
var
The old way to declare a variable. Avoid in new code — scoping rules are confusing.
var x = 1; // legacy
String
Text, in quotes. Template literals (backticks) let you embed variables.
const s = `Hi, ${name}!`;
Number
Any numeric value — no separate "integer" type.
const price = 19.99;
Boolean
true or false.
const isOpen = true;
Array
An ordered, numbered list of values.
const colors = ["red", "green", "blue"];
Object
Named key/value pairs.
const user = { name: "Ada", age: 30 };
null
Deliberately "no value", set by you.
let selected = null;
undefined
A variable that exists but hasn't been given a value yet.
let x; // undefined
typeof
Returns a string naming a value's type — useful for debugging.
typeof "hi"; // "string"
Use const by default, let when a value must change, and skip var entirely.
It signals intent to anyone reading your code and catches accidental reassignment as an error.
Operators
Operator
Definition
Example
+ - * / %
Add, subtract, multiply, divide, remainder. + also joins strings.
"Hi " + name; 7 % 2; // 1
=== / !==
Strict equal / not equal — compares value and type. Use these, always.
Read/write data-* attributes as camelCase properties.
<li data-id="7"> → li.dataset.id
Changing style & classes
Property / method
Definition
Example
el.style.property
Set one inline CSS property (camelCase name). Fine for one-off dynamic values.
el.style.backgroundColor = "gold";
el.classList.add(x)
Add a class — the usual way to trigger a CSS-defined visual state.
el.classList.add("open");
el.classList.remove(x)
Remove a class.
el.classList.remove("open");
el.classList.toggle(x)
Add it if missing, remove it if present — perfect for on/off UI.
el.classList.toggle("active");
el.classList.contains(x)
Is that class currently applied?
if (el.classList.contains("open")) …
Prefer classList over el.style. Define the look in CSS,
then use JS only to flip a class on or off. It keeps behaviour and presentation separate and
makes the state easy to find in your stylesheet.
Events
Concept
Definition
Example
el.addEventListener(type, fn)
Run fn whenever that event fires on el.
btn.addEventListener("click", () => alert("Hi"));
"click"
A mouse click (or tap) on the element.
btn.addEventListener("click", handleClick);
"input"
Fires immediately as a text field's value changes, keystroke by keystroke.
field.addEventListener("input", handleInput);
"change"
Fires once a value is committed (field loses focus, or a select/checkbox changes).
select.addEventListener("change", handleChange);
"submit"
Fires when a <form> is submitted. Almost always paired with preventDefault().
form.addEventListener("submit", handleSubmit);
"keydown"
A key is pressed. Check event.key for which one.
input.addEventListener("keydown", e => { if (e.key === "Enter") … });
event.target
The exact element the event happened on.
btn.addEventListener("click", e => console.log(e.target));
event.preventDefault()
Stops the browser's default action (following a link, submitting a form).
form.addEventListener("submit", e => e.preventDefault());
Creating & removing elements
Method
Definition
Example
document.createElement(tag)
Makes a new element, not yet on the page.
const li = document.createElement("li");
parent.appendChild(el)
Adds el as the last child of parent.
list.appendChild(li);
parent.append(el)
Like appendChild, but also accepts plain text and multiple arguments.
list.append(li, "some text");
el.remove()
Removes the element from the page entirely.
li.remove();
parent.insertBefore(new, ref)
Inserts new right before the ref child.
list.insertBefore(li, list.firstChild);
Console & debugging
Tool
Definition
Example
console.log()
Print a value to the DevTools console — your main debugging tool.
console.log("user is", user);
console.error() / console.warn()
Like log, but styled as an error/warning in the console.
console.error("Failed to save");
debugger;
Pauses execution right there if DevTools is open, so you can step through code.
function calc() { debugger; return total; }
Habits that save you time
Always use === / !==, never == / !=.
const by default; reach for let only when a value must change.
Name functions and variables for what they hold or do — isVisible, not flag.
Read the error message and the line number first — most bugs are exactly where it says.
Keep DOM lookups (querySelector) out of loops; look the element up once and reuse it.
Things to avoid
Don't assign user-typed text to innerHTML — it can inject markup/scripts. Use textContent.
Don't compare with ==; type coercion produces surprises like "" == 0 being true.
Don't forget break in a switch — execution "falls through" into the next case.
Don't rely on var — its function-wide scoping causes bugs let/const avoid.
CSS Selectors & the Cascade
A property does nothing until a selector aims it at
elements. This is how you say "these ones".
Basic selectors
Selector
Matches
Example
*
Every element. Handy for a reset; slow if overused.
* { box-sizing: border-box; }
p
All elements of that type.
p { margin-bottom: 1rem; }
.card
Elements with class="card". The workhorse — prefer these.
.card { padding: 1rem; }
#hero
The one element with id="hero". High specificity — use sparingly.
A quick list is below. For the syntax, the
nth formula, the -child vs -of-type
families, and :is() / :where() / :has(),
see Pseudo-Classes in Depth in the sidebar.
Selector
Matches
Example
:hover
Pointer is over the element.
a:hover { text-decoration: underline; }
:focus / :focus-visible
Element is focused / focused via keyboard. Style focus — don't hide it.
button:focus-visible { outline: 2px solid; }
:active
Being clicked/pressed.
button:active { transform: translateY(1px); }
:visited
A link the user has been to.
a:visited { color: purple; }
:first-child / :last-child
First / last among its siblings.
li:last-child { border-bottom: none; }
:nth-child(n)
By position: 2, odd, even, 3n. Great for zebra striping.
tr:nth-child(even) { background: #f8fafc; }
:not(sel)
Everything except what matches sel.
li:not(.active) { opacity: .6; }
:checked / :disabled / :required
Form-control states.
input:disabled { background: #eee; }
:root
The <html> element — where custom properties usually live.
:root { --brand: #2563eb; }
:is() / :where()
Shorten grouped selectors. :where() adds zero specificity.
:is(h1,h2,h3) code { font-size: .9em; }
Pseudo-elements (style a sub-part)
Selector
Matches
Example
::before / ::after
Inserts generated content; needs a content value.
.req::after { content: " *"; color: red; }
::first-line / ::first-letter
The first line / first letter of a block (drop caps).
p::first-letter { font-size: 2em; }
::placeholder
Placeholder text in an input.
input::placeholder { color: #94a3b8; }
::selection
Text the user has highlighted.
::selection { background: #fde68a; }
::marker
A list item's bullet or number.
li::marker { color: #2563eb; }
Specificity & the cascade
When two rules set the same property on the same element, the winner is
decided by, in order: 1)!important
(avoid it), 2) specificity — inline style >
#id > .class / [attr] /
:pseudo-class > element / ::pseudo-element,
3) source order — the last matching rule wins.
Keep specificity low and flat. Favour single class
selectors (.btn-primary) over long chains
(header nav ul li a.link). Low, even specificity makes CSS
predictable and easy to override later.
Pseudo-Classes in Depth
A pseudo-class targets an element that is in a certain
state (hovered, checked, disabled…) or
position (first, last, every third…). This page
covers the syntax, the cryptic nth formula, the
look-alike families, and the modern
:is() / :where() / :not() / :has()
group.
One colon or two?: is a pseudo-class — "match an element
that is something" (:hover,
:checked, :first-child).
:: is a pseudo-element — "style a
part of an element, or invent one"
(::before, ::marker,
::selection). This page is only the one-colon kind.
1 · Reading the syntax
Attach it directly to a selector, no space:
button:hover. A space would mean "a descendant" —
button :hover is something else entirely.
Chain as many as you like; all must be true
at once: a:hover:focus,
input:not(:disabled):checked.
Some take an argument in parentheses
(functional pseudo-classes):
:nth-child(2n+1), :not(.hidden),
:is(h1, h2, h3), :has(> img),
:lang(fr).
It only affects the part of the selector it is glued to. In
nav li:first-child > a, the
:first-child applies to the li; then
> a selects that li's child link.
/* the pointer is over the button */
.btn:hover { background: #1d4ed8; }
/* a link that is BOTH not .active AND being hovered */
nav a:not(.active):hover { text-decoration: underline; }
/* the <a> inside the FIRST <li> of any nav list */
nav ul li:first-child > a { font-weight: 700; }
/* every checkbox that is checked and enabled */
input[type="checkbox"]:checked:enabled { accent-color: green; }
2 · The An + B formula
:nth-child(), :nth-of-type(),
:nth-last-child() and
:nth-last-of-type() all take a formula
An + B. To read it, plug in
n = 0, 1, 2, 3, … and every result that lands on a
real position matches. A is the step, B is
the offset.
Formula
Matches (positions)
Reads as
3
just the 3rd
a fixed position (same as 0n+3)
odd = 2n+1
1, 3, 5, 7, …
every 2nd, starting at 1
even = 2n
2, 4, 6, 8, …
every 2nd, starting at 2
3n
3, 6, 9, …
every 3rd (n=1→3, n=2→6…; n=0→0 is skipped)
3n+1
1, 4, 7, 10, …
every 3rd, starting at 1
3n-1 = 3n+2
2, 5, 8, 11, …
every 3rd, starting at 2
n+3
3, 4, 5, 6, …
the 3rd onward (A=1, so every one, offset by 3)
-n+3
1, 2, 3
the first 3 only (A is negative, so it counts down)
2n+3
3, 5, 7, 9, …
every 2nd, starting at 3
n
every element
1n+0 — matches all
Select a range. Combine two nths:
li:nth-child(n+3):nth-child(-n+7) matches items 3 through
7 (the 3rd onward and within the first 7).
3 · -child vs -of-type
The whole positional family comes in two flavours.
:*-child counts every sibling regardless
of tag. :*-of-type counts only siblings with the
same tag.
Counts all siblings
Counts same-tag siblings
Meaning
:first-child
:first-of-type
the first one
:last-child
:last-of-type
the last one
:only-child
:only-of-type
the only one (no siblings)
:nth-child(…)
:nth-of-type(…)
by position from the start
:nth-last-child(…)
:nth-last-of-type(…)
by position from the end
The classic trap.p:first-child does
not mean "the first <p>". It means "a
<p> that is also the first child of its parent" —
so it matches nothing here, because the first child is the
<h2>:
<section>
<h2>Title</h2>
<p>First paragraph.</p> <!-- you want no top-margin here -->
<p>Second paragraph.</p>
</section>
/* MISSES: the first child is the <h2>, not a <p> */
p:first-child { margin-top: 0; }
/* WORKS: the first <p> among the <p> siblings */
p:first-of-type { margin-top: 0; }
4 · Other structural pseudo-classes
Selector
Matches
Example
:root
The top element — <html>. Higher priority than a plain html selector; the home of custom properties.
:root { --brand: #2563eb; }
:empty
An element with no child elements and no text — not even a space. (A newline and indentation count as text, so it must be truly <div></div>.)
p:empty { display: none; }
:nth-child(2 of .card)
The 2nd element that matches .card among its siblings (the "of S" syntax — newer, filters before counting).
li:nth-child(odd of :not(.hidden))
5 · Link & interaction states
Selector
Matches
Notes
:link
An <a> with an href that has not been visited.
Rarely needed; usually just style a.
:visited
A link the browser has in history.
Privacy-limited: only color, background-color, border-color, outline-color can change, and only to opaque colours.
:hover
The pointer is over the element (or a child).
Doesn't exist on touch — never hide anything essential behind it.
:active
Between mouse-down and mouse-up — the "being pressed" moment.
:focus
The element has keyboard/programmatic focus.
:focus-visible
Focused and the browser thinks a focus ring should show (i.e. keyboard, not a mouse click).
Use this for outlines so mouse users don't see a ring on click.
:focus-within
This element, or anything inside it, has focus.
Great for highlighting a form row or an open dropdown's container.
:target
The element whose id matches the current URL #hash.
:target { background: #fff3bf; } to flash a linked section.
Order matters: "LoVe / HAte" — L V H A.:link, :visited, :hover,
:active. They all have the same specificity, so the
last matching rule wins. Put :hover before
:visited and a visited link will never show its hover
colour. (Add :focus just before :hover.)
/* the safe link recipe, in the right order */
a:link { color: #2563eb; }
a:visited { color: #7c3aed; }
a:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
a:hover { color: #1d4ed8; }
a:active { color: #b91c1c; }
/* light up a whole form row while any field inside it is focused */
.field:focus-within { background: #eff6ff; }
/* highlight the section you jumped to via a #link */
:target { scroll-margin-top: 5rem; background: #fef9c3; }
6 · Form & input states
Selector
Matches
:checked
A ticked checkbox / radio, or a selected <option>.
:indeterminate
A checkbox set to the "half" state via JavaScript, or a radio group with nothing chosen.
:default
The control that is the default in its group (a pre-checked radio, the submit button).
:disabled / :enabled
Has / doesn't have the disabled attribute.
:required / :optional
Has / doesn't have the required attribute.
:read-only / :read-write
Can't / can be edited by the user.
:placeholder-shown
An empty field that is currently showing its placeholder text.
:valid / :invalid
Passes / fails its built-in constraints (type, pattern, required…). Applies immediately, even before the user touches it.
:user-valid / :user-invalid
Like the above, but only after the user has interacted with the field. Usually what you actually want for error styling.
:in-range / :out-of-range
A numeric / date input inside or outside its min–max.
:autofill
A field the browser has auto-filled.
/* show the error state only once the user has left an invalid field */
input:user-invalid { border-color: #dc2626; }
input:user-invalid + .error-msg { display: block; }
/* style a checkbox's own label by targeting the next sibling */
input[type="checkbox"]:checked + label { font-weight: 700; }
/* …or the label WRAPPING the checkbox, with :has() */
label:has(input:checked) { background: #dbeafe; }
/* a "floating label" that lifts once the field has content */
.float input:not(:placeholder-shown) + label { transform: translateY(-1.2em) scale(.85); }
/* mark required fields with an asterisk, no extra markup */
label:has(+ input:required)::after { content: " *"; color: #dc2626; }
7 · The logical group: :is():where():not():has()
These four take a selector list as their argument and
are how you build the "pre-defined combinations" — they replace
long, repetitive selectors.
Selector
Matches
Specificity
:not(X)
Elements that do not match X. Takes a list: :not(h1, h2, h3) = "none of these".
Adds the specificity of its most specific argument (:not(.x) counts as a class).
:is(A, B, C)
Matches if the element matches any of A/B/C. Factor out repetition.
Takes the specificity of its most specific argument.
:where(A, B, C)
Identical to :is() in what it matches…
…but always contributes zero specificity. Use it for base / reset styles you want to be trivially overridable.
:has(Y)
An element that contains something matching Y (the "parent selector"). Y is relative: :has(> img), :has(+ p), :has(h2).
Most specific argument, like :is().
/* :is() — one rule instead of three */
:is(section, article, aside) > h2 { margin-top: 0; }
/* …without :is() you'd write: */
section > h2, article > h2, aside > h2 { margin-top: 0; }
/* :where() — zero-specificity base styles, easy to override later */
:where(ul, ol) { margin: 0; padding: 0; }
/* :not() — every list item except the last gets a divider */
li:not(:last-child) { border-bottom: 1px solid #e2e8f0; }
/* :has() — style the container based on its contents */
.card:has(img) { padding-top: 0; } /* cards WITH an image */
article:has(> h2) { border-left: 3px solid; } /* direct-child h2 */
h2:has(+ p) { margin-bottom: .3em; } /* h2 right before a p */
form:has(:invalid) button { opacity: .5; } /* disable submit look */
.gallery:has(img:hover) img:not(:hover) { opacity: .4; } /* dim the rest */
Forgiving vs. strict.:is() and
:where() quietly ignore any argument the browser doesn't
understand, so one unknown selector won't kill the whole rule.
:not() is strict — an invalid argument invalidates
the selector.
8 · Quick reference — every pseudo-class
Selector
Matches
Example
:hover
pointer over it
.btn:hover
:active
being pressed
.btn:active
:focus
has focus
input:focus
:focus-visible
focus that should show a ring
a:focus-visible
:focus-within
it or a descendant has focus
.row:focus-within
:link / :visited
unvisited / visited link
a:visited
:target
id matches the URL hash
:target
:root
the <html> element
:root{--x:1}
:empty
no children, no text
td:empty
:first-child / :last-child
first / last of all siblings
li:last-child
:only-child
has no siblings
p:only-child
:nth-child(f) / :nth-last-child(f)
by position from start / end
tr:nth-child(even)
:first-of-type / :last-of-type
first / last of that tag
p:first-of-type
:only-of-type
the only one of that tag
img:only-of-type
:nth-of-type(f) / :nth-last-of-type(f)
by position among same tag
p:nth-of-type(2)
:not(S)
does not match S
:not(.active)
:is(S, …)
matches any of the list
:is(h1,h2) a
:where(S, …)
like :is(), zero specificity
:where(a) {}
:has(S)
contains something matching S
.card:has(img)
:checked
ticked / selected control
input:checked
:indeterminate
the "mixed" checkbox state
input:indeterminate
:default
the default control in a group
option:default
:disabled / :enabled
disabled / not disabled
button:disabled
:required / :optional
required / not required field
input:required
:read-only / :read-write
not editable / editable
input:read-only
:placeholder-shown
empty field showing its placeholder
input:placeholder-shown
:valid / :invalid
passes / fails constraints (immediately)
input:invalid
:user-valid / :user-invalid
…but only after the user interacts
input:user-invalid
:in-range / :out-of-range
numeric value inside / outside min–max
input:out-of-range
:autofill
browser-autofilled field
input:autofill
:lang(xx) / :dir(rtl)
content language / text direction
:lang(fr) q
:defined
a defined element (built-in, or a registered custom element)
my-widget:defined
:modal
an element in modal state (an open <dialog>.showModal())
dialog:modal
:popover-open
a popover that is currently showing
[popover]:popover-open
:fullscreen
the element shown fullscreen
video:fullscreen
9 · Gotchas to remember
:first-child means "is the first child", not "the
first child of this type". Use
:first-of-type for the latter.
:nth-child counts every sibling;
:nth-of-type counts only same-tag siblings. They give
different results whenever the children are mixed tags.
::before, ::after,
::first-line are pseudo-elements (two
colons). They are not in this list.
Link states must be ordered L V H A
or later rules will hide earlier ones.
:visited can only change a few colour properties, and
JavaScript can't read whether it matched — both for privacy.
:not() and :is()keep the
specificity of their most specific argument.
:where() is the only one that adds nothing.
A newline and indentation inside a tag count as text, so
:empty won't match an element that merely looks empty
in your source.
Units, Colors & Functions
Length units
Unit
What it is
Use for
px
One CSS pixel (absolute).
Borders, small fixed details, hairlines.
rem
Relative to the root font size (usually 16px).
Font sizes and spacing. Scales with user settings; predictable.
em
Relative to this element's font size.
Spacing that should track the local text size (padding inside a button).
%
Relative to the parent's corresponding dimension.
Fluid widths inside a container.
vw / vh
1% of the viewport width / height.
Full-screen sections, hero heights. Watch mobile address bars — see dvh.
dvh / svh / lvh
Dynamic / small / large viewport height.
100dvh for reliable full-height on mobile.
ch
Width of the "0" character.
Line length: max-width: 65ch for readable paragraphs.
fr
A fraction of leftover space (Grid only).
grid-template-columns: 2fr 1fr;
Writing colors
Form
Example
Notes
Named
color: rebeccapurple;
~150 keywords. Fine for prototyping.
Hex
#2563eb / #25f / #2563ebcc
3, 6, or 8 digits (last pair = alpha).
rgb()
rgb(37 99 235 / 0.8)
Modern syntax uses spaces + / alpha.
hsl()
hsl(220 83% 53%)
Hue-Saturation-Lightness — easy to tweak by hand.
oklch()
oklch(0.62 0.19 255)
Perceptually even; great for generating consistent palettes.
currentColor
border-color: currentColor;
Reuses the element's color value.
transparent
background: transparent;
Fully see-through.
Contrast. Body text should hit a contrast ratio of at
least 4.5:1 against its background (3:1 for large
text). Check with your browser's dev-tools color picker.
Useful functions
Function
What it does
Example
calc()
Mix units in one value.
width: calc(100% - 2rem);
min() / max()
Pick the smaller / larger of several values.
width: min(90%, 1100px);
clamp()
min, preferred, max — fluid type without media queries.
Real markup for real pages — whole-page skeletons first, then the
components that go inside them. Every block is commented. Copy one, swap
the words for your own, and you have an accessible starting point.
The mental model. A page is like a textbook:
<main> is the book, <article> is a
self-contained chapter, <section> is a sub-topic with
its own heading, and <p> holds the sentences. Nest
your tags to match that outline and you get a logical document,
keyboard/screen-reader navigation, and better SEO for free.
Whole-page skeletons
1. The complete document
Every page starts here. The <head> is configuration
the visitor never sees; the <body> is everything they
do.
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Always first: character encoding -->
<meta charset="UTF-8">
<!-- Makes the page scale correctly on phones -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Shown in the browser tab and search results -->
<title>Sourdough for Beginners – The Bakehouse</title>
<!-- The grey text under the title in Google results -->
<meta name="description" content="A step-by-step guide to your first loaf.">
<!-- Tab icon, and social-share preview -->
<link rel="icon" href="/favicon.svg">
<meta property="og:title" content="Sourdough for Beginners">
<meta property="og:image" content="https://example.com/loaf.jpg">
<!-- Your styles. Put <script> near </body> or use defer. -->
<link rel="stylesheet" href="/styles.css">
<script src="/app.js" defer></script>
</head>
<body>
<!-- Visible content lives here -->
</body>
</html>
2. A standard content page
The chrome that wraps almost every page: a skip link for keyboard
users, the site header and navigation, one <main>,
and a footer.
One <h1> per page. It names the
whole page. Everything else steps down: <h2> for
sections, <h3> inside those. Never skip a level to
get a smaller size — style it with CSS instead.
3. A blog post / article page
An <article> is content that would still make sense
on its own (in a feed reader, shared on its own URL). Its
<header> carries the title and byline; each
<section> is a topic within it.
<main>
<article>
<!-- Article intro: title, author, date, hero image -->
<header>
<h1>How to Bake Sourdough Bread</h1>
<p class="byline">
By <a rel="author" href="/authors/john-doe">John Doe</a>
<!-- machine-readable date, human-readable text -->
<time datetime="2026-03-14">March 14, 2026</time>
</p>
<figure>
<img src="/loaf.jpg" alt="A cross-section of a sourdough loaf" width="800" height="500">
<figcaption>An open, airy crumb is the goal.</figcaption>
</figure>
</header>
<!-- First topic -->
<section>
<h2>The Ingredients</h2>
<p>Flour is the most important component of your bread.</p>
<p>You will also need filtered water and salt.</p>
</section>
<!-- Second topic -->
<section>
<h2>The Instructions</h2>
<ol>
<li>Mix the ingredients in a large bowl.</li>
<li>Let the dough rise for at least four hours.</li>
<li>Bake at 450°F for 40 minutes.</li>
</ol>
</section>
<footer>
<p>Filed under
<a href="/tags/bread" rel="tag">bread</a>,
<a href="/tags/beginner" rel="tag">beginner</a>.
</p>
</footer>
</article>
<!-- Related to the page but not part of the article itself -->
<aside aria-label="Related recipes">
<h2>You might also like</h2>
<ul>
<li><a href="/no-knead">No-knead bread</a></li>
<li><a href="/focaccia">Rosemary focaccia</a></li>
</ul>
</aside>
</main>
4. A marketing / landing page
A run of full-width <section>s, each with its own
heading. The hero, the value props, social proof, and a final call to
action.
<main>
<!-- Hero: the one-sentence pitch + primary action -->
<section class="hero">
<h1>Ship your side project this weekend</h1>
<p>Starter kits, hosting, and a domain in one click.</p>
<a class="btn btn-primary" href="/signup">Start free</a>
<a class="btn btn-ghost" href="#features">See how it works</a>
</section>
<!-- Features: a heading + a list of cards -->
<section id="features" aria-labelledby="features-h">
<h2 id="features-h">Everything included</h2>
<ul class="feature-grid">
<li>
<h3>One-click deploy</h3>
<p>Push to main and we build it.</p>
</li>
<li>
<h3>Free SSL</h3>
<p>HTTPS on every domain, automatically.</p>
</li>
<li>
<h3>Global CDN</h3>
<p>Fast everywhere, no config.</p>
</li>
</ul>
</section>
<!-- Social proof -->
<section aria-labelledby="quote-h">
<h2 id="quote-h" class="visually-hidden">What customers say</h2>
<figure>
<blockquote><p>I launched in an afternoon. Genuinely.</p></blockquote>
<figcaption>— Priya N., <cite>indie developer</cite></figcaption>
</figure>
</section>
<!-- Closing call to action -->
<section class="cta">
<h2>Ready to build?</h2>
<a class="btn btn-primary" href="/signup">Create your account</a>
</section>
</main>
Section vs. div. Reach for
<section> when the group of content has a heading and
belongs in the page outline. If you only need a box to hang CSS on,
that's a <div>. A <section> with
no heading is usually a <div> in disguise.
5. Documentation layout (sidebar + content)
Two columns: a navigation <aside> and the
<main> article. CSS Grid or Flexbox places them side
by side; the markup order still puts content first.
<div class="docs-layout">
<!-- Secondary navigation for this docs area -->
<aside class="docs-nav">
<nav aria-label="Documentation">
<h2>Getting started</h2>
<ul>
<li><a href="/docs/install">Installation</a></li>
<li><a href="/docs/config" aria-current="page">Configuration</a></li>
<li><a href="/docs/deploy">Deploying</a></li>
</ul>
</nav>
</aside>
<main class="docs-content">
<article>
<h1>Configuration</h1>
<p>All settings live in <code>config.json</code>.</p>
<section>
<h2>Environment variables</h2>
<p>Prefix custom values with <code>APP_</code>.</p>
<pre><code>APP_PORT=3000</code></pre>
</section>
<section>
<h2>Reloading</h2>
<p>Changes apply on the next request in dev mode.</p>
</section>
<!-- Prev / next pager -->
<nav class="pager" aria-label="Docs pages">
<a rel="prev" href="/docs/install">← Installation</a>
<a rel="next" href="/docs/deploy">Deploying →</a>
</nav>
</article>
</main>
</div>
6. App / dashboard shell
A fixed top bar, a persistent side nav, and a <main>
work area built from <section> panels. Landmarks
(header, nav, main) let power
users jump straight to a region.
Every field has a real <label>. Related choices go in
a <fieldset> with a <legend>. Help
and error text are linked with aria-describedby so screen
readers announce them.
<form action="/subscribe" method="post">
<div class="field">
<label for="email">Email address</label>
<input
type="email" id="email" name="email"
autocomplete="email" required
aria-describedby="email-help">
<p id="email-help" class="help">We'll only email you the newsletter.</p>
</div>
<!-- A group of radio buttons: fieldset + legend is the label for the group -->
<fieldset>
<legend>How often?</legend>
<label><input type="radio" name="freq" value="weekly" checked> Weekly</label>
<label><input type="radio" name="freq" value="monthly"> Monthly</label>
</fieldset>
<div class="field">
<label for="name">Name <span class="opt">(optional)</span></label>
<input type="text" id="name" name="name" autocomplete="name">
</div>
<label class="check">
<input type="checkbox" name="terms" required>
I agree to the <a href="/terms">terms</a>.
</label>
<!-- A real submit button, not a <div onclick> -->
<button type="submit">Subscribe</button>
</form>
Show errors as text, next to the field — not
just a red border. Link it with
aria-describedby="email-error" and set
aria-invalid="true" on the input.
10. A data table with a totals row
<caption> titles the table.
<th scope> tells assistive tech which cells are
headers for which row or column. <tfoot> holds
summary rows.
On small screens, wrap the table in
<div style="overflow-x:auto"> so it can scroll
sideways instead of breaking the layout.
11. Figures & responsive media
<figure> groups a visual with its caption.
<picture> serves a different image per screen size or
format; the <img> inside is the always-works
fallback.
<!-- Image with a caption -->
<figure>
<img src="/diagram.png"
alt="Data flows from the client to the API to the database"
width="900" height="500">
<figcaption>Figure 1. The request lifecycle.</figcaption>
</figure>
<!-- Different crop on phones, modern format when supported -->
<picture>
<source media="(min-width: 800px)" srcset="/hero-wide.avif" type="image/avif">
<source media="(min-width: 800px)" srcset="/hero-wide.jpg">
<source srcset="/hero-square.jpg">
<img src="/hero-square.jpg" alt="Chef plating a dish" width="600" height="600">
</picture>
<!-- Video with captions and a poster frame -->
<figure>
<video controls width="640" poster="/thumb.jpg">
<source src="/demo.webm" type="video/webm">
<source src="/demo.mp4" type="video/mp4">
<track kind="captions" src="/demo.en.vtt" srclang="en" label="English" default>
</video>
<figcaption>A two-minute product tour.</figcaption>
</figure>
12. Breadcrumbs
A trail back to the home page. It's an ordered list —
the sequence matters — inside a labelled
<nav>. The last item is the current page and isn't a
link.
<nav aria-label="Breadcrumb">
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/guides">Guides</a></li>
<li><a href="/guides/bread">Bread</a></li>
<!-- Current page: text only, marked with aria-current -->
<li><span aria-current="page">Sourdough</span></li>
</ol>
</nav>
13. An FAQ (no JavaScript)
<details> is a built-in show/hide widget.
<summary> is the always-visible question; everything
after it is the answer.
<section aria-labelledby="faq-h">
<h2 id="faq-h">Frequently asked questions</h2>
<details>
<summary>Do I need a sourdough starter?</summary>
<p>Yes — either make one over a week or get some from a friend.</p>
</details>
<details>
<summary>Can I use all-purpose flour?</summary>
<p>You can, but bread flour gives a better rise.</p>
</details>
<!-- Add the "open" attribute to have one expanded by default -->
<details open>
<summary>How long does it keep?</summary>
<p>Three days at room temperature, or slice and freeze.</p>
</details>
</section>
14. A comment thread
Each comment is its own <article> (it stands alone
and has an author + date). Replies nest inside the comment they answer.
<section aria-labelledby="comments-h">
<h2 id="comments-h">3 comments</h2>
<article class="comment">
<header>
<img src="/u/sam.jpg" alt="" width="40" height="40">
<strong>Sam</strong>
<time datetime="2026-03-15T09:12">2 hours ago</time>
</header>
<p>Worked on the first try. Thank you!</p>
<!-- A reply is a nested article -->
<article class="comment reply">
<header>
<strong>John Doe</strong> <small>(author)</small>
<time datetime="2026-03-15T10:03">1 hour ago</time>
</header>
<p>So glad it helped!</p>
</article>
</article>
<article class="comment">
<header>
<strong>Priya</strong>
<time datetime="2026-03-15T11:20">40 minutes ago</time>
</header>
<p>What hydration do you recommend for a beginner?</p>
</article>
</section>
15. A testimonial / pull quote
<blockquote> is the quoted words;
<figure> + <figcaption> attribute
it. <cite> is the title of a work or the source, not
a person's name.
<figure class="testimonial">
<blockquote cite="https://example.com/review/42">
<p>
This is the guide I wish I'd had when I started. Clear,
no fluff, and my loaf actually rose.
</p>
</blockquote>
<figcaption>
— Dana Reyes, home baker,
reviewing <cite>Sourdough for Beginners</cite>
</figcaption>
</figure>
Across all of these: write the HTML in the order the
content should be read (content before sidebars), give every
<img> an alt, keep one
<h1> and don't skip heading levels, and use a real
<button> or <a> for anything
clickable. Then let CSS place things wherever you like.
Border-Image Slicer
border-image takes one picture and cuts it into 9 pieces
with 4 lines: the 4 corners keep their exact size, the
4 edges stretch or repeat along each side, and the
middle is dropped (or kept, with fill).
Drop in a frame or button-skin image, drag the guides to where the
stretchable part begins, and copy the CSS.
0
0
0
0
top
right
bottom
left
px on the box
px past the edge
Live preview — drag the bottom-right corner to resize and
watch the corners hold while the edges fill.
Resize me. The corners should stay sharp; the edges stretch,
repeat, round, or space depending on the setting.
Reading the values.border-image-slice: 12 20 30 40 means "cut 12 from the
top, 20 from the right, 30 from the bottom, 40 from the left" —
the usual top / right / bottom / left order. One number applies to all
four sides. Plain numbers are source-image pixels; add
% to slice by percentage of the image instead.
Always set a real border-width and a
plain border fallback. border-image paints
over the border area but reserves no layout space on its own
— with no border width there is nothing to paint into, and
browsers without border-image support fall back to the
plain border.
Live Code Editor
Type HTML and CSS in the boxes on the left; the result renders
instantly on the right. Load a preset to see a pattern in full.