Here are your HTML/CSS rules so far, consolidated into one reference.
HTML & CSS Basics — Rules So Far
| # | Rule | Key point |
|---|---|---|
| 1 | Vertical margins can collapse | Adjacent vertical margins on normal block elements can collapse. The resulting gap is generally the larger margin, not the sum. Flex/grid items and several other layout situations do not collapse this way. |
| 2 | Text contrast | Normal text should have at least 4.5:1 contrast against its background. Large text can use 3:1. |
| 3 | Center a block with auto margins | Give it an appropriate width and use margin-left: auto; margin-right: auto;, commonly margin: 0 auto;. A parent’s text-align: center does not replace this for block children. Still works on a flex container itself; use flex alignment for its children. |
| 4 | text-align: center centers inline content |
Set it on the parent to center text, images, spans, and other inline/inline-block content. It does not center normal block children — those still need width + margin: 0 auto (rule 3). |
| 5 | Know block vs inline | Block elements normally start a new line and take available width. Inline elements flow with surrounding content and generally take only the space they need. |
| 6 | display can change default behavior |
An element's default block/inline behavior can be changed with display. |
| 7 | Use Flexbox for flexible layouts | display: flex makes the direct children flex items. The flex container is still a block-level box (full width by default) unless you change that. |
| 8 | justify-content controls the main axis |
Packs items along the main axis: flex-start, flex-end, center, space-between, space-around, space-evenly. |
| 9 | align-items controls the cross axis |
Aligns items along the cross axis: stretch (default), flex-start, flex-end, center, baseline. |
| 10 | Prefer gap for spacing between flex items |
gap (or row-gap / column-gap) adds space between items only — not outside the edges. Use margin when one item needs unique spacing. |
| 11 | Lean on inheritance | Properties like color, font-size, and font-family inherit. Set them once on a parent instead of repeating them on every child. |
| 12 | Keep CSS DRY | Share styles through common selectors or classes. Avoid copying the same declarations onto many one-off rules when one shared rule can do the job. |
| 13 | Understand relative paths | ./ = current directory, ../ = one directory up, ../../ = two up. |
| 14 | Understand root-relative paths | / starts from the current origin's root rather than the current file's directory. This matters when an app is deployed under a subpath such as /my-app/. |
| 15 | Write meaningful alt text |
Describe the image's relevant meaning as though explaining it to someone over the phone. |
| 16 | Don't write “image of” in alt |
Screen readers already communicate that the element is an image. |
| 17 | Keep alt concise |
<125 characters is a useful guideline, not a strict HTML/WCAG requirement. |
| 18 | Decorative images use alt="" |
This allows assistive technology to ignore images that provide no meaningful information. |
| 19 | alt is primarily accessibility |
It also helps when images fail to load and can help search engines understand image content. |
| 20 | Use system/web-safe fonts when appropriate | They are simple, fast, and reliable. Use web fonts when the design needs them, but always include sensible fallbacks. |
| 21 | Always include font backups | If you set font-family, never list only one font. Provide at least one named backup when useful, and always end with a generic family (serif, sans-serif, monospace, or cursive) so the browser can still pick something sensible. |
| 22 | Box shorthand is clockwise: T → R → B → L | Four values map to top, right, bottom, left. The same order applies to margin, padding, and border sides (border-width, border-style, border-color). |
| 23 | Find color schemes on Coolors | Use coolors.co to explore, generate, or extract a palette, then apply those hex colors in your CSS (background, color, borders, accents). After picking colors, still check text contrast (rule 2). |
| 24 | Use Unsplash, Pexels, or undraw for imagery | Free stock photos from Unsplash or Pexels are fine for practice/projects. Flat illustration heroes may use undraw.co (or a local undraw-style SVG). Attribution is usually appreciated rather than required, but always check the current license and usage terms. |
| 25 | div vs span |
Use div as a generic block container and span as a generic inline container. Prefer semantic elements such as section, article, nav, header, and footer when they describe the content meaningfully. |
| 26 | Use inherit when you want the parent’s value |
inherit makes a property take its parent’s computed value. Useful to undo a browser default, pass a parent style into a child that normally would not inherit that property, or keep nested text matching its container. |
| 27 | Google Fonts are fine for practice | Load a font from Google Fonts with <link> tags in <head> (not CSS @import, which blocks rendering), then use it in font-family with backups (rule 21). |
| 28 | Local fonts via @font-face (TTF/OTF) |
Download a font (e.g. from 1001 Fonts), put the .ttf / .otf in your project, declare it with @font-face, then use the family name — always with backups. |
| 29 | Be careful with externally hosted animation assets | WebP can be smaller than GIF, but avoid depending on hotlinked CDN URLs unless the service permits it. Check embedding and licensing terms. |
| 30 | Use an overlay on photo backgrounds | Text on a busy background image often fails contrast (rule 2). Add a dark (or light) overlay between the photo and the content so text and controls stay readable. |
| 31 | text-shadow can improve readability |
It may help text stand out on busy backgrounds, but do not rely on it to satisfy contrast requirements. Prefer a proper overlay or stronger foreground/background contrast. |
| 32 | Always set lang on <html> |
Use a language code such as lang="en". Screen readers use it for pronunciation; search engines use it for language; browsers use it for language-related features (quotes, hyphenation, fonts). |
| 33 | Circular photos: equal size + border-radius: 50% |
Give the image equal width and height, add object-fit: cover to avoid distortion, then use border-radius: 50%. |
| 34 | Pill buttons use a huge radius | Large horizontal padding + border-radius: 999px (or another very high value) makes a capsule / pill shape. |
| 35 | Soft card lift with box-shadow |
A light shadow (e.g. 0 4px 12px rgba(0, 0, 0, 0.15)) lifts a box off the page without a hard border. |
| 36 | Full-bleed band / breakout | A section with full available width and no side margins can run a color or photo edge to edge as a visual band. For partial breakout, use a 4-column grid with outer minmax(0, 1fr) gutters and inner content tracks, then grid-area so media spans into a gutter while copy stays in the constrained column. |
| 37 | Truncate long text with ellipsis | overflow: hidden; white-space: nowrap; text-overflow: ellipsis cuts a single line with “…”. |
| 38 | Soft square (squircle) | Medium border-radius (about 12px–24px) softens corners without going fully circular. |
| 39 | Same-size thumbnails | Set both width and height, then use object-fit: cover so images stay even without being stretched. |
| 40 | Two-tone section rhythm | Alternate backgrounds (e.g. .box / .box-alt) to create visual rhythm without complex layout. |
| 41 | Strong header band as a visual anchor | A tall, high-contrast header/nav band can “anchor” the page even without position: sticky. |
| 42 | flex-direction sets the main axis |
row (default) = main axis horizontal; column = main axis vertical. Also row-reverse and column-reverse. Changing direction swaps what justify vs align control. |
| 43 | :hover — pointer is over the element |
Styles the “pointer over me” state. Keep contrast (rule 2); don’t hide essential info only behind hover (touch). |
| 44 | :focus — element currently has focus |
Styles inputs, buttons, and links while focused. Keep focus obvious for accessibility. Later, learn :focus-visible for keyboard-focused UI states. Never use bare outline: none (or outline: 0) without a visible replacement in the same ruleset. |
| 45 | :active — element is being pressed |
Applies during the click/press. Often a slightly darker or tighter look than :hover. |
| 46 | :visited — link already opened |
Styles a links the user has visited. Browsers limit what you can change (often mainly color) for privacy. |
| 47 | :first-child — first among siblings |
Targets the first child element in a parent (e.g. first li in a list). |
| 48 | :nth-child(...) — child by pattern |
Picks children by number or pattern, e.g. :nth-child(odd) / :nth-child(even) for zebra rows, or :nth-child(3) for the third. |
| 49 | Specificity decides which selector wins | When multiple CSS rules target the same property on the same element, the selector with higher specificity usually wins. Think roughly: inline styles > IDs > classes/attributes/pseudo-classes > element selectors. |
| 50 | Later rules win when specificity ties | If two competing rules have the same specificity and both apply, the rule that appears later in the stylesheet wins. |
| 51 | Avoid overly specific selectors | Prefer simple reusable classes over long selector chains or IDs for styling. High specificity makes later overrides harder. :is() can shorten lists, but its specificity is that of its most specific argument. |
| 52 | Group selectors to share styles | Separate selectors with commas when they need the same declarations, e.g. h1, h2, h3 { font-family: Georgia, serif; }. Prefer :is(h1, h2, h3) when nesting or repeating the same list. |
| 53 | Group related content semantically | Use meaningful containers such as section, article, header, nav, main, and footer when they describe the content. Use div when no semantic element fits. |
| 54 | Understand the complete box model | Every element is built from the inside out: content → padding → border → margin. With the default content-box, declared width and height apply only to the content. |
| 55 | Prefer box-sizing: border-box for predictable sizing |
With border-box, declared width and height include content, padding, and border. Margin always remains outside the declared size. |
| 56 | Combine width with min-width and max-width |
width proposes a size, while min-width and max-width set lower and upper limits. A common responsive pattern is width: 100%; max-width: 960px;. Use min-width carefully because it can force horizontal overflow. |
| 57 | Prefer flexible height constraints | Use min-height when a box needs a minimum size but may grow with content. Use max-height only when you have decided what excess content should do; a fixed height can clip or overflow changing content. |
| 58 | Choose CSS units by what should control the size | Use px for deliberate fixed details, % for a containing block, rem for root-relative sizing, em for component-relative sizing, and viewport units when the viewport should control the result. |
| 59 | Choose overflow behavior deliberately | visible is the default; hidden clips; auto adds scrolling only when needed; scroll reserves scrolling behavior even when content fits. Prefer auto for content users must still reach. |
| 60 | Element selectors target a tag name | A selector such as h2 matches every h2 element. Use it for broad defaults that should apply to every instance of an element. |
| 61 | Class selectors create reusable styles | A selector such as .card matches every element with class="card". Prefer classes for reusable component and utility styles. |
| 62 | Descendant selectors use ancestry | A selector such as .card img matches every img anywhere inside an element with class card. Keep descendant selectors short so they remain easy to override. |
| 63 | Attribute selectors match attributes | A selector such as input[type="email"] matches inputs whose type is email. They are especially useful for distinguishing form controls. |
| 64 | Commas group selectors | A selector list such as h1, h2, h3 applies one declaration block to every listed selector and avoids duplicated CSS. :is() groups the same way inside a larger selector (e.g. nav :is(a, button):hover). |
| 65 | static keeps normal flow |
position: static is the default. The element stays in normal flow, and offset properties do not move it. |
| 66 | relative preserves layout space |
A relatively positioned element keeps its original place in normal flow, while offsets move only its rendered box. It can also establish the containing block for absolute descendants. |
| 67 | absolute leaves normal flow |
An absolutely positioned element no longer reserves layout space. It uses the nearest positioned ancestor as its containing block, or an initial containing block when none exists. |
| 68 | fixed follows the viewport |
A fixed element normally stays at the same viewport location while the document scrolls. Reserve space and test zoom so it does not hide content or controls. |
| 69 | sticky needs a threshold |
A sticky element participates in normal flow until scrolling reaches an offset such as top: 0. Ancestor overflow and available scroll space affect whether it sticks. |
| 70 | z-index controls intentional overlap |
Positioned or layered elements with a larger z-index appear above lower layers within the same stacking context. Prefer a small documented scale instead of arbitrary huge values. |
| 71 | Stacking contexts contain z-index | A child cannot use a large z-index to escape its ancestor’s stacking context. Compare ancestors when a z-index seems ineffective. Use isolation: isolate on a section root to create a local stacking context without changing layout. |
| 72 | Use one primary main |
Put the page’s unique primary content in one visible main. Repeated site navigation, page headers, and page footers normally remain outside it. |
| 73 | section groups a theme |
Use section for a thematic group that normally has a heading. Do not use it as a generic styling wrapper. |
| 74 | article stands independently |
Use article for content that can make sense on its own or be reused, such as a post, product card, review, or news item. |
| 75 | nav identifies major navigation |
Wrap major navigation link groups in nav. Label multiple navigation regions so users can distinguish them. |
| 76 | header and footer follow their scope |
At page level they introduce or conclude the document; inside a section or article they introduce or conclude that nearest piece of content. |
| 77 | aside is complementary |
Use aside for related but nonessential content. Visual placement in a sidebar does not by itself make content an aside. |
| 78 | Use figure for a referenced unit |
Group media with its caption in figure and figcaption when they form one self-contained unit referenced by the surrounding content. |
| 79 | Prefer native meaning over redundant ARIA | Choose the correct HTML element first. Do not add a role that merely repeats the element’s built-in semantics. |
| 80 | Keep headings logical | Use headings to describe the document hierarchy, not to obtain a particular font size. Keep one <h1> per page, never skip levels (e.g. h2 → h4), and use paragraphs for ordinary body copy. |
Semantic HTML
Semantic HTML communicates what content means and how regions relate. It does not prescribe a visual design; CSS can restyle semantic elements without removing their meaning.
Use this decision order:
| Content relationship | Element |
|---|---|
| Unique primary page content | main |
| Thematic group with a heading | section |
| Independently meaningful or reusable item | article |
| Major navigation link group | nav |
| Introductory content for a page, section, or article | header |
| Closing metadata for a page, section, or article | footer |
| Complementary content | aside |
| Media and caption that form one unit | figure + figcaption |
| No accurate semantic relationship | div |
A page should normally have one visible main. A section usually needs a
heading, while an article should still make sense when separated from the
surrounding page. Headers and footers are scoped by where they are nested.
Use one <h1> for the page’s primary title. Nest headings without skipping
levels — an h2 is followed by h3, not h4. Skipping (for example h2 →
h4) treats heading level as a font-size shortcut and breaks the outline
assistive technology exposes.
Multiple navigation regions need distinct accessible names:
<nav aria-label="Primary navigation">...</nav>
<nav aria-label="Related articles">...</nav>
Use native elements instead of duplicating their semantics:
<main>...</main>
Do not write <div role="main"> when main itself is available, and do not add
role="navigation" to nav. ARIA is useful when native HTML cannot express the
needed name, state, or relationship; it is not a replacement for choosing the
correct element.
Common CSS selectors
Selectors appear before the opening brace of a CSS rule and decide which HTML elements receive its declarations. Start with these common forms:
| Selector type | Example | What it matches |
|---|---|---|
| Element | h2 |
Every h2 element |
| Class | .card |
Every element with class="card" |
| Descendant | .card img |
Every img inside .card |
| Attribute | input[type="email"] |
Email inputs |
| Grouped | h1, h2 |
Every h1 and every h2 |
Prefer simple classes for reusable styles. Use descendant selectors when the ancestor is an important part of the context, and group selectors when they share the same declarations.
Positioning and stacking
Normal flow should remain the default. Position an element only when its visual relationship cannot be expressed clearly by ordinary document flow or layout.
| Value | Flow behavior | Positioned relative to |
|---|---|---|
static |
Remains in normal flow | Normal layout; offsets are ignored |
relative |
Keeps its original space | Its normal position |
absolute |
Leaves normal flow | Nearest positioned ancestor |
fixed |
Leaves normal flow | Usually the viewport |
sticky |
Keeps flow space, then sticks | Its scroll container and offset threshold |
Offsets (top, right, bottom, and left) describe where a positioned edge
should sit. Avoid setting opposing offsets unless the intended size behavior is
clear.
For an anchored badge, position the parent and then position the badge:
.card {
position: relative;
}
.badge {
position: absolute;
top: 0.75rem;
right: 0.75rem;
}
For sticky positioning, provide a threshold:
.section-nav {
position: sticky;
top: 0;
z-index: 10;
}
z-index matters only when elements can overlap, and its comparison is bounded
by stacking contexts. Start with source order, then use a small scale such as
content 0, sticky navigation 10, and fixed utility controls 20. Do not use
values such as 999999 as a substitute for understanding the relevant
ancestors.
When a section contains layered decoration (absolute media, accents, floating
UI), create a local stacking context on that section root with
isolation: isolate. Internal z-index values then stay local and cannot paint
over a neighbouring section.
CSS specificity
Specificity is the browser's way of deciding which CSS rule should win when more than one rule tries to set the same property on the same element.
A useful beginner order is:
| Selector type | Example | Relative strength |
|---|---|---|
| Element | p |
Low |
| Class / attribute / pseudo-class | .card, [type="text"], :hover |
Medium |
| ID | #hero |
High |
| Inline style | style="color: red" |
Very high |
p {
color: black;
}
.notice {
color: blue;
}
#warning {
color: red;
}
<p id="warning" class="notice">Important</p>
The text is red because the ID selector is more specific than the class and element selectors.
If specificity is equal, the rule written later wins:
.card { color: blue; }
.card { color: green; } /* wins */
Prefer simple class-based styling. Avoid fighting specificity with very long selectors or !important unless you have a clear reason.
Grouping elements and selectors
There are two related ideas worth separating:
Grouping HTML content
Use containers to group related content. Prefer semantic elements when they describe what the group means:
<section class="services">
<h2>Services</h2>
<p>What we offer.</p>
</section>
Use div when you need a generic grouping box and no semantic element fits:
<div class="card-actions">
<button>Save</button>
<button>Cancel</button>
</div>
Use span when you need to group or style part of a line without creating a new block:
<p>Total: <span class="price">$99</span></p>
Grouping CSS selectors
If several selectors share the same declarations, separate them with commas:
h1,
h2,
h3 {
font-family: Georgia, serif;
}
/* Same idea with :is() when the list sits inside a larger selector */
nav :is(a, button):hover {
color: #0e7490;
}
This keeps CSS DRY and prevents repeated declarations. :is() takes the
specificity of its most specific argument, so prefer simple arguments.
Pseudo-classes
Pseudo-classes style an element in a special state or position. Write them with a colon: button:hover, input:focus, li:first-child.
Style the default rule first, then add pseudo-class overrides. Live demos: pseudo-classes/.
Interaction states
| Pseudo-class | When it applies | Typical use |
|---|---|---|
:hover |
Pointer is over the element | Buttons, links, cards |
:focus |
Element is focused (Tab / click into field) | Inputs, buttons, links — visible focus for a11y |
:active |
Element is being pressed | Brief “pressed” look on buttons |
:visited |
Link has been visited | a color after the user opens it |
button {
background: #0e7490;
color: #ecfeff;
}
button:hover {
background: #155e75;
}
button:active {
background: #0f766e;
}
input:focus {
border: 2px solid #0e7490;
background: #ecfeff;
}
a:visited {
color: #6b21a8; /* browsers may restrict other properties */
}
Tips:
-
You can change more than color: background, background-image, font-size, padding, border, even which text shows (two
spans, toggle withdisplay) -
Hover/focus/active text and backgrounds must still meet contrast (rule 2)
-
Never hide critical content only behind
:hover -
:focusmatters for keyboard users — don’t remove focus styles without a clear replacement -
Never use bare
outline: noneoroutline: 0. If you restyle focus, put the replacement outline (or equivalent ring) in the same ruleset as the removal -
Live demos in
pseudo-classes/show size, image, and label swaps — not only color
/* Font grows on hover */
.grow:hover {
font-size: 20px;
}
/* Background image swaps on hover */
.photo-card:hover {
background: url("other.jpg") center center no-repeat;
background-size: cover;
}
/* Swap visible text with two spans */
.btn .alt {
display: none;
}
.btn:hover .main {
display: none;
}
.btn:hover .alt {
display: inline;
}
Structural (position among siblings)
| Pseudo-class | When it applies | Typical use |
|---|---|---|
:first-child |
Element is the first child of its parent | Emphasize the first list item or card |
:nth-child(odd) / :nth-child(even) |
Odd or even children | Zebra-stripe rows |
:nth-child(3) |
The 3rd child | Spot-target one item |
.list li:first-child {
font-weight: 700;
}
.list li:nth-child(odd) {
background: #f8fafc;
}
.list li:nth-child(even) {
background: #dceaf7;
}
Document language (lang)
Every page should open with a language on the root element:
<!DOCTYPE html>
<html lang="en">
-
Accessibility — screen readers pick the correct voice and pronunciation for that language
-
Search / indexing — helps search engines understand the page language
-
Browser features — supports consistent language-related behavior (e.g. hyphenation, typographic defaults)
Match the code to the content (en, fil, ja, …). If a passage is in another language, you can set lang on that element too.
Circular photos
To make an image look round (avatars, profile shots):
-
Set a width (and usually the same height) so the box is square
-
Set
border-radius: 50%— half of each side curves into a full circle
.avatar {
width: 160px;
height: 160px;
border-radius: 50%;
}
If width and height differ, 50% makes an ellipse, not a perfect circle. Prefer a square box for circular photos.
Simple cool tricks
Handy one-liners and small patterns (live demos: special/tricks.html):
| Trick | Recipe | Also see |
|---|---|---|
| Circular photo | Square width/height + border-radius: 50% |
Rule 33 |
| Pill button | Padding + border-radius: 999px |
Rule 34 |
| Soft card lift | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) |
Rule 35 |
| Full-bleed band / breakout | Full-width band, or 4-track grid with gutter breakout | Rule 36 |
| Centered page column | width + margin: 0 auto |
Rule 3 |
| Truncate text | overflow: hidden; white-space: nowrap; text-overflow: ellipsis |
Rule 37 |
| Soft square | border-radius: 12px–24px |
Rule 38 |
| Tinted / readable photo text | Overlay div or layered background |
Rule 30 |
| Halo text | text-shadow: 0 0 5px black |
Rule 31 |
| Even icon/item row | display: flex + justify-content: space-between or center |
Rules 7–8 |
| Skip decorative images | alt="" |
Rule 18 |
| Same-size thumbnails | Fixed width and height on each img |
Rule 39 |
| Accent a word | <span class="accent">…</span> inside the sentence |
Rule 25 |
| Two-tone sections | Alternate background classes | Rule 40 |
| Visual page anchor | High-contrast header/nav band | Rule 41 |
Flexbox
Set display: flex on a parent. Only its direct children become flex items.
Main axis vs cross axis
flex-direction |
Main axis (→ justify-content) |
Cross axis (→ align-items) |
|---|---|---|
row (default) |
left → right | top ↕ bottom |
row-reverse |
right → left | top ↕ bottom |
column |
top → bottom | left ↔ right |
column-reverse |
bottom → top | left ↔ right |
.row {
display: flex;
flex-direction: row; /* main axis horizontal */
}
.stack {
display: flex;
flex-direction: column; /* main axis vertical */
}
justify-content (main axis)
| Value | Effect |
|---|---|
flex-start |
Pack toward the start of the main axis |
flex-end |
Pack toward the end |
center |
Center along the main axis |
space-between |
First at start, last at end; equal gaps between |
space-around |
Equal space around each item (half-size at edges) |
space-evenly |
Equal space everywhere, including edges |
align-items (cross axis)
| Value | Effect |
|---|---|
stretch |
Items fill the cross size (default) |
flex-start |
Align to the start of the cross axis |
flex-end |
Align to the end |
center |
Center on the cross axis |
baseline |
Align text baselines |
To center a group both ways inside a tall/wide box:
.stage {
display: flex;
justify-content: center; /* main */
align-items: center; /* cross */
height: 200px;
}
gap
.row {
display: flex;
gap: 16px; /* space between items only */
}
gap does not add padding on the outside of the container. Live demos: flexbox/01-playground.html.
Overlay for background contrast
Photo heroes and posters look great with background-size: cover, but bright/busy areas can wash out text. Put a translucent layer on top of the image, then put your content on that layer.
Option A — nested overlay div (clear and easy to tune):
<div class="hero">
<div class="hero-overlay">
<h1>Title</h1>
</div>
</div>
.hero {
background: url("photo.jpg") center center no-repeat;
background-size: cover;
}
.hero-overlay {
padding: 48px 24px;
background: rgba(15, 23, 42, 0.65); /* dark scrim */
color: #f8fafc;
}
Option B — layered background (image + gradient scrim in one box):
.hero {
background:
linear-gradient(rgba(15, 23, 42, 0.65), rgba(15, 23, 42, 0.65)),
url("photo.jpg") center center no-repeat;
background-size: cover, cover;
color: #f8fafc;
}
Use a light overlay (rgba(255, 255, 255, 0.75)) when the text is dark. Re-check contrast after you pick the opacity.
text-shadow
text-shadow paints a shadow behind glyphs. The common form is:
text-shadow: offset-x offset-y blur-radius color;
| Part | Meaning | Example in 0px 0px 5px black |
|---|---|---|
offset-x |
Horizontal shift (positive = right) | 0px (no side shift) |
offset-y |
Vertical shift (positive = down) | 0px (no up/down shift) |
blur-radius |
How soft/wide the shadow is | 5px |
color |
Shadow color | black |
When background/text contrast is weak (busy photo, similar colors), zero the offsets and use a higher blur so the shadow acts like a soft halo or outline around the letters:
/* Soft readable glow — offsets zeroed, blur does the work */
h1 {
color: #ffffff;
text-shadow: 0px 0px 5px black;
}
/* Stronger haze on a very busy image */
h1 {
text-shadow: 0 0 8px rgba(0, 0, 0, 0.9);
}
Light text usually needs a dark shadow; dark text usually needs a light shadow (0 0 6px white). You can stack multiple shadows if needed. Still aim for solid contrast with an overlay when possible (rules 2 and 30).
div vs span
| Tag | Default display | Use it when… |
|---|---|---|
div |
block | You need a box/section for layout: page wrapper, card, nav group, row. |
span |
inline | You need to style part of a sentence without starting a new line. |
*<!-- layout chunk → div -->*
<div class="card">...</div>
*<!-- phrase inside text → span -->*
<h3>Hello, <span class="accent">Alex</span></h3>
How inherit works
Most text properties (color, font-family, font-size) already inherit. inherit forces any property to copy the parent:
body {
color: #1a1a1a;
font-family: "Georgia", serif;
}
/* Links often keep a browser-blue color — inherit pulls the parent's color */
a {
color: inherit;
}
/* Non-inherited properties can also inherit if you ask */
.child {
background: inherit; /* same background as parent */
}
Set shared styles on a parent (body / .page), let children inherit, and only override when needed (rules 11–12).
Google Fonts
-
Pick a font on fonts.google.com.
-
Add the provided
<link>tags in<head>(often a stylesheet link; preconnect links help too). Do not load fonts with CSS@import— that waits for the stylesheet first and blocks rendering longer than a<link>in<head>. -
Use the family in CSS with backups:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;700&display=swap" rel="stylesheet">
body {
font-family: "Outfit", Arial, sans-serif;
}
Local TTF / OTF fonts (1001 Fonts and similar)
-
Download a font from 1001 Fonts (or another licensed source). Read the license; give credit if required.
-
Put the file in your project, e.g.
fonts/MyFont.ttf. -
Declare it once in CSS, then use the name:
@font-face {
font-family: "MyFont";
src: url("./fonts/MyFont.ttf") format("truetype");
/* OTF example: url("./fonts/MyFont.otf") format("opentype"); */
}
.hero-title {
font-family: "MyFont", Georgia, serif;
}
Multi-word family names need quotes. Always end with a generic fallback (rule 21).
The complete box model
Every rendered element is a set of nested boxes:
-
Content — text, images, or child elements
-
Padding — space between the content and border
-
Border — the edge surrounding content and padding
-
Margin — transparent space outside the border
The default sizing mode is content-box:
.card {
box-sizing: content-box;
width: 300px;
padding: 20px;
border: 2px solid;
}
Its visible width is 300 + 40 + 4 = 344px, before margins.
With border-box, the declared width includes content, padding, and border:
.card {
box-sizing: border-box;
width: 300px;
padding: 20px;
border: 2px solid;
}
The visible width remains 300px. Margin is always outside that width.
Width constraints
.page {
box-sizing: border-box;
width: 100%;
max-width: 960px;
min-width: 280px;
margin: 0 auto;
}
-
widthsupplies the preferred size. -
max-widthprevents the box from becoming too wide. -
min-widthprevents it from becoming too narrow, but can cause horizontal overflow on a smaller viewport.
Height constraints
.card {
min-height: 240px;
}
min-height creates a minimum while still allowing content to make the box taller. Prefer it over a fixed height for text-heavy components.
max-height limits growth, but it does not decide how excess content is handled. Content may spill out unless an appropriate overflow rule is also chosen. Do not use max-height blindly on changing text.
CSS units
Choose a unit based on what should control the size:
| Unit | Relative to | Common use |
|---|---|---|
px |
A CSS reference pixel | Borders, small fixed details, deliberate component limits |
% |
Usually the relevant size of the containing block | Fluid widths and proportional layouts |
rem |
The root element’s font size | Consistent spacing and type across the page |
em |
Font size in the element’s current context | Components that scale with their text |
vw |
1% of viewport width | Viewport-relative widths and display type |
vh |
1% of viewport height | Viewport-height sections |
em needs context: for font-size, it is based on the parent’s font size; for many other properties, it is based on the element’s computed font size. Nested em sizing can compound. rem avoids that compounding by always returning to the root.
Modern viewport units
Mobile browser controls can change the visible viewport. Newer units make the intended behavior explicit:
-
svh/svw— small viewport; assumes browser controls take their largest space. -
lvh/lvw— large viewport; assumes browser controls are retracted. -
dvh/dvw— dynamic viewport; updates as browser controls appear and disappear.
Use dvh for a section that should follow the currently visible height. Use svh when content must fit even while browser controls are visible. Test viewport-sized layouts on mobile.
Overflow
Overflow occurs when content is larger than the dimensions or constraints of its box.
| Value | Result |
|---|---|
visible |
Default. Content may paint outside the box. |
hidden |
Clips content outside the box. Do not use it to hide information users need. |
auto |
Adds scrolling only when content does not fit. Usually the safest choice for reachable content. |
scroll |
Creates a scroll container even when content fits; scrollbars may remain visible depending on the platform. |
.panel {
max-height: 20rem;
overflow: auto;
}
A size constraint and overflow answer different questions: the constraint defines how large the box may become; overflow defines what happens to excess content.
Box shorthand (margin, padding, border)
Values go clockwise, starting at the top:
| Position | Longhand example | In margin: 10px 20px 30px 40px |
|---|---|---|
| Top | margin-top |
10px |
| Right | margin-right |
20px |
| Bottom | margin-bottom |
30px |
| Left | margin-left |
40px |
So these are equivalent:
margin-top: 10px;
margin-right: 20px;
margin-bottom: 30px;
margin-left: 40px;
/* same as */
margin: 10px 20px 30px 40px;
Two values: margin: 10px 20px
With two values, the first is top and bottom, the second is left and right:
| Position | Longhand | In margin: 10px 20px |
|---|---|---|
| Top | margin-top |
10px |
| Right | margin-right |
20px |
| Bottom | margin-bottom |
10px |
| Left | margin-left |
20px |
margin-top: 10px;
margin-right: 20px;
margin-bottom: 10px;
margin-left: 20px;
/* same as */
margin: 10px 20px;
The same top → right → bottom → left order applies to:
-
padding(e.g.padding: 10px 20px 30px 40px) -
border sides via
border-width,border-style, andborder-color(e.g.border-width: 1px 2px 3px 4px)
Value counts:
-
1 value — all four sides
-
2 values — top/bottom, then left/right
-
3 values — top, left/right, bottom
-
4 values — top, right, bottom, left
Font categories (generic families)
| Category | What it looks like | Common use |
|---|---|---|
serif |
Letters have small decorative strokes (“feet”) at the ends of strokes. | Body text, traditional / print feel |
sans-serif |
Without those feet — cleaner, more geometric letterforms. | UI, headings, screen reading |
monospace |
Every character takes the same width (like a typewriter). | Code, IDs, aligned columns |
cursive |
Looks like handwriting or calligraphy. | Decorative accents (use sparingly) |
Pick a named font that matches the category you want, then end the stack with that same generic category.
Web-safe fonts (W3Schools)
| Font | Generic fallback |
|---|---|
| Arial | sans-serif |
| Verdana | sans-serif |
| Tahoma | sans-serif |
| Trebuchet MS | sans-serif |
| Times New Roman | serif |
| Georgia | serif |
| Garamond | serif |
| Courier New | monospace |
| Brush Script MT | cursive |
Example: font-family: Arial, Helvetica, sans-serif;
A useful theme behind all of these: HTML describes the content and its meaning; CSS controls how that content is presented and laid out. Prefer inheritance and shared rules so you repeat less.
Advanced Flexbox
- Use
flex-wrap: wrapwhen a row should form additional lines instead of squeezing or overflowing.wrap-reversereverses the cross-axis line direction, not the source order. - Treat
flex-basisas an item’s starting main size before the flex algorithm distributes free space. - Use
flex-growto divide positive free space by relative factors; a value of0opts out of growth. - Use
flex-shrinkto resolve negative free space. Content and minimum-size constraints can limit how far an item shrinks. - Read the common shorthand
flex: grow shrink basisin that order. For example,flex: 1 1 14remmeans grow 1, shrink 1, basis 14rem. - Use
align-selfto override cross-axis alignment for one flex item. It does not move an entire flex line. - Use
align-contentonly for multiple wrapped lines with spare cross-axis space. Usealign-itemsoralign-selffor items within each line. - Keep HTML in a meaningful reading and keyboard order.
orderchanges visual placement only and must not be used to repair poor source order.
Advanced Flexbox separates three decisions: wrapping decides whether new lines form, flexible sizing distributes main-axis space, and alignment controls items or lines on the cross axis. Visual rearrangement is optional; accessible source order is not.
CSS Grid
- Use
display: gridwhen rows and columns should work together as a two-dimensional layout. Grid properties apply to the container's direct children. - Define explicit columns with
grid-template-columnsand explicit rows withgrid-template-rows. Grid lines are the numbered boundaries around those tracks. - Use
grid-columnandgrid-rowto place an item by start and end lines. A value such as1 / span 2starts at line 1 and covers two tracks. - Use
grid-template-areasand matchinggrid-areanames when a readable layout map is clearer than line numbers. Every named area must form one complete rectangle. - Keep source order meaningful. Grid placement changes visual position, not reading or keyboard order.
- Use
repeat()to express repeated track patterns without copying the same track size. - Use
minmax()to give a track a useful minimum and flexible maximum, such asminmax(14rem, 1fr). - Inside
repeat(),auto-fitcollapses empty repeated tracks so existing items can expand;auto-fillpreserves the empty tracks. - Use
justify-itemsandalign-itemsto align grid items inside their own grid areas on the inline and block axes. - Use
justify-contentandalign-contentto align the whole track collection inside the container. They need spare space to produce a visible result.
Full-bleed breakout (rule 36): a page-wide grid of four columns — outer minmax(0, 1fr) gutters plus two inner content tracks — lets copy sit in a constrained column while an image uses grid-area to spill into a side gutter on wide viewports.
Grid separates track sizing, item placement, and alignment. Item alignment moves boxes inside cells; content alignment moves the track collection inside its container.
Responsive CSS and pseudo-elements
- Choose media-query breakpoints where the content needs a different layout. Do not build a breakpoint list from device names.
- Prefer a mobile-first base when practical: write a usable narrow layout first, then add
@media (min-width: ...)or equivalent range-syntax enhancements such as@media (width >= ...)as space becomes available. - Keep media-query overrides near or after the base declarations they change so the cascade remains easy to follow.
- Use
max-width: 100%withheight: autowhen an image should shrink inside its container while preserving its intrinsic proportions. - Use
aspect-ratiowhen the layout needs a predictable image slot. Pair it with an intentional width or height. - Use
object-fit: coverto fill an image slot when cropping is acceptable; usecontainwhen the entire image must remain visible. ::beforeand::aftergenerate child boxes around an element's content. They need thecontentproperty to appear.- Keep essential words, instructions, status, and controls in HTML. Pseudo-element content is presentation and is not a reliable replacement for accessible document content.
Responsive design starts with content that can adapt, then adds conditional layout changes only where needed. Responsive images protect their containers; pseudo-elements add optional presentation without changing source meaning.
Responsive navigation without JavaScript
- Use native
detailsandsummaryfor a simple disclosure menu when JavaScript is not available. The browser provides keyboard operation and exposes the open/closed state to assistive technology. - Give the control an accessible name. Visible
Menutext is clearest; when the design requires an icon-only hamburger, usearia-label="Menu"onsummaryand keep the three lines decorative. - Keep navigation links in meaningful source order. At the content-driven breakpoint, hide the desktop link group and show the disclosure menu so only one navigation set is exposed at a time.
- Treat the CSS-only pattern as progressive navigation, not a full application menu. It does not automatically close after a link is chosen or when a user clicks elsewhere; add JavaScript later only when that extra behavior is required.
<details class="mobile-menu">
<summary aria-label="Menu">
<span class="hamburger-icon"><span></span><span></span><span></span></span>
</summary>
<div class="mobile-menu-panel">
<a href="#features">Features</a>
<a href="#pricing">Pricing</a>
</div>
</details>
Use a media query to keep this disclosure hidden while the full navigation fits, then swap the two presentations when the links need more room. For an icon-only control, list-style: none and summary::-webkit-details-marker { display: none; } remove the native chevron. Every clickable target needs a hit area of at least 24×24 CSS pixels (WCAG 2.2 Target Size Minimum); about 44×44 px remains a comfortable touch target for primary controls. Do not use a hidden checkbox as a substitute for a button: a checkbox communicates form state rather than menu disclosure state.
Accessible forms
- Give every form control a visible label. Prefer an explicit
labelwhoseforvalue matches the control's uniqueid. - Treat placeholders as examples or short hints, never as replacements for labels. Placeholder text disappears during entry and may be missed or misunderstood.
- Choose an input
typethat matches the requested data. Appropriate types can provide suitable mobile keyboards, browser validation, and autofill behavior. - Give every submitted control a meaningful
name. In ordinary form submission, successful controls without names do not contribute values. - Use recognized
autocompletetokens when a field requests familiar user information such as a name, email address, telephone number, or address. - Use
fieldsetandlegendto give a shared name to related radio buttons or checkboxes that answer one question. - Keep detailed instructions visible. Associate separate help text with its control using
aria-describedbywhen the relationship would otherwise be unclear. - Prefer native constraints such as
required,minlength,maxlength,min, andmaxbefore recreating those meanings with ARIA. - Identify required fields and errors in text, not through colour alone. A coloured border can support a message but cannot replace it.
- Give every
buttonan explicittype:submitsends the form,resetrestores initial values, andbuttonhas no default submission action. - Preserve a logical source and keyboard order, and keep focus indicators clearly visible on every interactive control.
- Use native inputs, selects, textareas, and buttons instead of rebuilding their behavior with generic elements and ARIA.
<form method="get">
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email" required>
<label for="topic">Topic</label>
<select id="topic" name="topic">
<option value="accessibility">Accessibility</option>
<option value="layout">Layout</option>
</select>
<label for="message">Message</label>
<textarea id="message" name="message" aria-describedby="message-help"></textarea>
<p id="message-help">Include the result you expected.</p>
<button type="submit">Send</button>
</form>
Accessible forms start with names and relationships, not appearance. Labels name individual controls, legends name groups, help text explains expectations, and native elements provide keyboard behavior and semantics before CSS is added.
Tables and lists
- Use
ulwhen item order does not change meaning andolwhen sequence, ranking, or count matters. - Put list content inside
lielements. A nested list belongs inside thelithat owns that subgroup. - Use
dl,dt, andddfor directly associated term/description, name/value, or question/answer groups—not merely to indent content. - Use
tableonly for data whose row and column relationships matter. Do not use tables to arrange page layout. - Give each data table a concise
captionthat identifies its subject or purpose. - Use
thfor headers andtdfor data. Visual boldness on a data cell does not create a header relationship. - In a simple table, add
scope="col"to column headers andscope="row"to row headers. - Use
thead,tbody, andtfootwhen rows have those distinct purposes. Row groups organize structure but do not replace header cells. - Keep table source order logical and use
colspanorrowspanonly when a cell truly covers related columns or rows. - Prefer simple tables. Complex multi-level headers need an explicit, carefully tested association strategy.
- On narrow screens, put a wide table inside a labelled
overflow: autowrapper. Do not turn table elements into blocks and destroy their row/column relationships. - Keep list markers when they communicate grouping or sequence. If presentation removes them, provide another visible treatment that preserves that meaning.
<table>
<caption>Workshop schedule</caption>
<thead>
<tr>
<th scope="col">Time</th>
<th scope="col">Topic</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">10:00</th>
<td>Accessible tables</td>
</tr>
</tbody>
</table>
Lists describe membership, sequence, or direct associations. Tables describe two-dimensional data relationships. Choose the structure from the content relationship first, then style it without changing what that structure means.
Advanced form controls and status
- Use
optgroupwith a meaningfullabelwhen select options need categories. The group label organizes choices but is not itself selectable. - Connect an input's
listattribute to a datalist'sid. Datalist options are suggestions, not restrictions; users may still enter another valid value. - Match specialized input types to the requested data:
search,url,date,number,file,range, andcolorexpose useful browser behavior but still need visible labels. - Use native range constraints such as
min,max, andstep, and explain required formats or limits in persistent text when browser presentation may vary. - Use
acceptto help users choose suitable files, not as validation. Validate file type, size, and content on the server. - A hidden input is not interactive and does not need a visible label, but its value is neither secret nor trustworthy. Treat submitted hidden values as untrusted input.
- Use
outputfor a calculated result, give it an accessible name, and use itsforattribute to identify the controls contributing to that result. - Use
progressfor completion of a task with a known maximum. Its minimum is always zero; omitvalueonly when progress is genuinely indeterminate. - Use
meterfor a scalar measurement within a known range, such as a score or storage level. It may definemin,max,low,high, andoptimum. - State important output, progress, and meter values in text. Native bars and colour changes can support the message but must not be the only way the value or meaning is communicated.
<label for="topic-search">Topic</label>
<input id="topic-search" name="topic" type="search" list="topics">
<datalist id="topics">
<option value="Accessible forms"></option>
<option value="Semantic tables"></option>
</datalist>
<label for="course-progress">Course completion</label>
<progress id="course-progress" value="4" max="6">4 of 6</progress>
<span>4 of 6 lessons complete</span>
Specialized controls improve input only when their native meaning matches the content. Preserve visible labels and instructions, expect browser interfaces to vary, validate submitted data on the server, and repeat important visual status as understandable text.
CSS motion
- Use transitions for state changes such as hover and focus. Define the base state and changed state, then add a duration greater than zero.
- Name each property that should transition instead of using
transition: all; explicit properties prevent unrelated future changes from animating. - Give pointer and keyboard users equivalent feedback. If hover and focus represent the same action, style both
:hoverand:focus-visible. A visible focus indicator must meet WCAG 2.2 Focus Appearance: at least 3:1 contrast against adjacent colours and at least a 2px thick perimeter (or equivalent area). Never remove the outline with bareoutline: none/outline: 0unless the same ruleset supplies that replacement. translate(),scale(), androtate()change the rendered box without changing the space it reserves in normal flow. Leave room when transformed content could overlap its surroundings.- Transform functions share one
transformvalue and are applied in sequence. Changing their order can change the final result. transform-originsets the point around which scaling and rotation occur; its default is the element's centre.- Use
@keyframeswhen motion needs intermediate stages or runs independently of a state change. Attach the keyframes withanimation-nameand a duration. - Animation properties control timing, delay, repetition, direction, and fill behavior. Prefer a finite animation and add only the controls the effect needs.
- Prefer animating
transformandopacitywhen practical because they usually avoid repeated layout work. - Keep motion brief and purposeful, never make it the only carrier of meaning, and use
@media (prefers-reduced-motion: reduce)to remove or reduce nonessential movement.
.button {
transition: background-color 200ms ease, transform 200ms ease;
}
.button:hover,
.button:focus-visible {
background-color: #174f52;
transform: translateY(-0.125rem);
}
@keyframes arrive {
from {
opacity: 0;
transform: translateY(1rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.button {
transition-duration: 0.01ms;
}
}
Transitions connect known states; transforms change rendered geometry; and keyframes describe multi-step animation. Motion should support an interface's meaning without becoming necessary to perceive or operate it.
CSS variables and colour themes
- A CSS custom property begins with two hyphens, such as
--color-brand. The declaration stores a value but does not apply that value to a visual property by itself. - Use semantic names that describe a role, such as
--color-surface,--color-text, and--color-border. Names such as--bluebecome misleading when a palette changes. - Declare document-wide values on
:root, then read them withvar(--property-name)wherever that colour role is needed. - Custom properties participate in the cascade and normally inherit. A declaration on a nearer ancestor can override a value from
:rootfor that ancestor and its descendants. - Override related colour roles together on a theme or component container. Changing only one side of a text/background pair can create unreadable contrast.
- Add a fallback with
var(--property-name, fallback)when a missing or invalid custom property would otherwise make the whole declaration invalid. - A fallback is not a contrast check. The browser does not use the fallback merely because a valid custom property contains a poorly chosen colour.
- Extract values that repeat or represent a clear design role. A custom property for every one-off literal adds indirection without making the stylesheet easier to maintain.
:root {
--color-surface: #ffffff;
--color-text: #1f2933;
--color-brand: #176b73;
--color-on-brand: #ffffff;
}
.card {
background: var(--color-surface);
color: var(--color-text);
}
.card-featured {
--color-surface: #0d4f56;
--color-text: #ffffff;
}
.button {
background: var(--button-background, var(--color-brand));
color: var(--button-text, var(--color-on-brand));
}
The root palette supplies shared defaults. The featured card overrides values only within its scope, while the button keeps readable defaults if its optional component variables are absent. Re-check contrast and visible interaction states for every palette; variables reduce repetition but do not guarantee accessibility.
Clip-path
- Use
clip-pathto hide parts of an element's paint with a geometric shape. The layout box (space the element occupies) does not shrink to match the visible silhouette. - Start with basic shapes:
circle(...),ellipse(...),inset(...), andpolygon(...). Leave SVGpath()andurl(#id)clips for later work. - Percentages in shape arguments are relative to the clipped element's own box.
- Keep required text, controls, and instructions inside the visible region. Content clipped away is hard or impossible to perceive.
- When clipping a photograph, keep a meaningful
alton theimg. The clip is presentation; the image meaning stays in HTML.
Glassmorphism
- Glassmorphism is a CSS presentation technique. Ordinary glass panels do not require JavaScript; add JavaScript only when blur or opacity must react dynamically to scrolling, pointer movement, or application state.
- Use a translucent
backgroundso detail behind the panel can remain partly visible. - Use
backdrop-filter: blur(...)to blur content behind a panel.filter: blur(...)instead blurs the element itself and is not a substitute. - Include
-webkit-backdrop-filterwith the same value for WebKit/Safari compatibility. - Give glass a subtle translucent border, rounded corners, and a restrained shadow so its edge remains visible.
- Put visible colour, imagery, gradients, or shapes behind the glass. A blur over a flat, featureless background may be technically active but visually imperceptible.
- Text contrast requirements still apply: at least 4.5:1 for normal text and 3:1 for large text. Increase the panel opacity or use a darker surface when the background makes content difficult to read.
body {
background:
radial-gradient(circle at 20% 20%, #7657ff, transparent 24rem),
#080b18;
}
.glass {
background: rgba(8, 11, 24, 0.65);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 1rem;
box-shadow: 0 0.5rem 2rem rgba(0, 0, 0, 0.2);
color: #ffffff;
backdrop-filter: blur(0.75rem);
-webkit-backdrop-filter: blur(0.75rem);
}
The recipe is translucent background + backdrop blur + subtle edge + sufficient
contrast. The Prism combined project demonstrates the treatment without
JavaScript in combine/16-prism-landing.html.
Admin UI and form presentation
- An admin dashboard shell usually separates a sidebar (or top navigation), a topbar with page title and actions, and a main canvas for content.
- Prefer a muted page background with white (or tokenized) surfaces for cards and forms so work areas read as raised planes.
- Choose one elevation system per product UI: border-only cards, soft shadow without a border, or a hairline border plus a soft shadow. Mixing all three on one screen usually looks inconsistent.
- Structure cards with optional header, body, and footer slots. Place overflow actions in the header rather than scattering them through the body.
- A metric card typically includes a label, a large value, and a trend line or pill. Communicate increase or decrease with text as well as colour.
- An icon well is a fixed square with a soft radius and tinted background beside a metric label. Mark decorative icons with
aria-hidden="true"when the label already names the metric. - An operational status strip (counts awaiting action) sits above the KPI row and is not a substitute for metric cards.
- Muted secondary text (dates, “vs last month”) uses lower emphasis than body copy while remaining readable against its background.
- Keep a radius scale: tighter radii on controls, larger radii on cards, and pill radii only for capsules and trend chips.
- An input group is a flex row that joins addons and a field behind one continuous border. Round only the outer corners of the group.
- Input-group addons are supplementary. Keep a visible field label (or group name) and connect helpful addon text with
aria-describedbywhen needed. - Soft admin fields often use a thin border, comfortable height (about 2.5rem–2.75rem), and a focus ring from
outlineor frombox-shadow. Never remove every visible focus style. - Size modifiers (small, default, large) should change padding, font size, and height together rather than only the corner radius.
- Disabled and readonly fields change background and border together. Do not rely on opacity alone when the value must stay readable.
- Form layouts include stacked fields, horizontal label/field pairs that stack on narrow viewports, multi-column grids, and compact inline toolbars.
- A switch presentation is still a native checkbox with a visible label. Style the control with CSS; do not rebuild toggle behaviour with empty elements in the HTML/CSS workbook.
- Floating labels still use a real
labelelement. Placeholders remain examples, not names. - Validation presentation pairs a border or ring change with persistent text. Do not communicate errors by colour alone.
- Dropzone chrome is presentation around a real
input type="file". Theacceptattribute suggests types; servers must still validate uploads. - Star ratings in CSS lessons are radio groups with styled labels so keyboard and assistive technology still work.
- Multi-step wizard chrome can mark current and completed steps in HTML. Changing steps and validating each step belongs to JavaScript.
Storefront patterns
- A storefront shell separates top navigation, optional category navigation, primary
maincontent, and a site footer. Name eachnavregion. - Product cards are appropriate browsing units: image, title link, price, and rating or badge text. Do not wrap the whole page in nested decorative cards.
- Filter catalogs place labelled fieldsets in an aside beside a product grid and stack the columns on narrow viewports.
- Product detail pages compose a gallery and a buy box. Colour and size choices need visible group names; quantity needs a visible label.
- Specification and invoice data belong in real tables with captions and headers, not in layout grids pretending to be tables.
- Cart and checkout summaries may use sticky positioning, but the summary must remain reachable in source order for keyboard users.
- Delivery and payment options are radio groups (or native selects) with helper text. Do not rely on card chrome alone to name the choice.
- Account hubs combine profile metrics, static tab chrome, and orders or reviews tables. Status pills use text as well as colour.
- Print-friendly invoices hide lesson and site chrome with
@media printwhile keeping address blocks and line-item tables readable.
Admin commerce patterns
- Table variant classes (striped, hover, bordered, compact) change presentation only. Keep semantic table structure from Chapter 13.
- Ops table chrome includes a toolbar, status pills, scroll wrapper, and optional row-action menus. Native
details/summarycan provide a static action menu without Bootstrap dropdown scripts. - Search fields and pagination controls may appear as non-functional chrome in CSS lessons. Wiring search, sort, and pages belongs to JavaScript Phase 9.
- Add-product layouts use a main details column and a pricing or stock sidebar. Reuse input-group prefixes for currency and vanity URLs.
- Shipping fulfilment choices are radio cards with short helper copy describing responsibility and cost.
- Admin list pages sit inside the dashboard shell: sidebar, topbar actions, toolbar, data table, and pagination chrome.
- Entity detail pages lead with identity and actions, then definition cards, then a related orders or items table.
Travel booking patterns
- A travel agency shell separates named primary navigation for booking verticals (such as Hotel, Flight, and Trip), primary
maincontent, and a site footer. - Booking search chrome uses labelled native fields for trip type, places, dates, and travelers. Do not replace those controls with third-party widgets in the HTML/CSS workbook.
- Fare catalogs place filter fieldsets in an aside beside result rows that show airline, times, duration, stops, price, and a clear Select action.
- Experience detail pages lead with title, price per person, and meta, then overview and itinerary content, with a sticky book box that stays reachable in source order.
- Stay detail pages offer room cards with amenities and price notes beside a sticky stay summary; hotel compare pages use a real attribute table, not a layout grid pretending to be a table.
- Booking checkout composes guest, house-rule, and payment fieldsets with a sticky booking summary for dates, party, fees, and total.
Landing / hero composition patterns
Named catalog demos live under ui/heroes/ (twenty-three patterns: fifteen
foundations plus eight Phase-2 stylistic systems). Phase-2 allow-list and
technique vocabulary live in CSS/hero-composition/allowed.md.
- A motif kit is two or three reusable shapes (for example teardrop, circle, soft square) plus an optional faint dot-grid utility. Reuse the same shapes as badge, cluster, and frame instead of inventing a new silhouette per accent.
- Text on photographic heroes still needs a translucent overlay or scrim
between the image and the copy (rules 30–31). Treat
text-shadowas a secondary aid, not the only contrast strategy. - Mask photos with
border-radius: 50%, organicclip-path, or an SVG blob container so the image has a deliberate footprint. Allow a controlled breakout (about 10–20%) past the mask edge for depth; keep headlines and primary CTAs in the safe unclipped region. - Plan an explicit z-index stack before decorating: field → geometry → media → floating UI → seam-overlapping pills or cards. Without a stack recipe, absolute accents collide and clip unpredictably on narrow viewports.
- Dual-tone headlines (accent colour on key words, ink on the rest) and a short accent rule or banner under the H1 sharpen hierarchy without adding extra marketing blocks.
- Compound hero widgets—contact pills, vertical social rails, multi-field search hubs, and overlapping feature pills—are valid when they are the named pattern’s job. On ordinary landing tops that are not those patterns, keep the first viewport to brand, one headline, one lede, one CTA group, and one dominant visual.
- Prefer soft, wide, low-opacity shadows and light blurred spheres (bokeh) for atmosphere. Avoid harsh multi-layer glow stacks and purple-on-white default themes in this workbook’s studio palette.
- A wave or soft shape under the hero is a section exit—a transition into the next band—not a substitute for content hierarchy or for readable type.
- Decorative motion and carousel autoplay must honor
prefers-reduced-motion. Every catalog hero includes a responsive.hero-navwith a burger at or below48rem. - Prefer inline SVG
<path>elements with Bézier commands (C,Q,S) when a smooth irregular wave or blob is the design idea. Useborder-radiusor polygonclip-pathas simpler CSS approximations while learning. Generators such as freesvgwaves.com (no attribution required) and getwaves.io / Haikei can produce pasteable inline paths; prefer local SVG over hotlinked assets (rule 230). Hero-field SVG waves (agency backgrounds) are distinct from the section-exit wave in rule 225. - Cutout photography places a transparent PNG/WebP (or transparent SVG) over
a field or organic shape instead of a rectangular framed photo. Give
meaningful subjects real
alttext; usealt=""when the cutout is purely decorative and nearby text already carries the meaning. - Document the stack before decorating: field → SVG geometry → cutout/media → copy and UI → seam-overlapping badge. Device mockups often sit on the seam between a light content field and a coloured SVG wave.
- Flat illustration heroes may use undraw.co or a local
undraw-style SVG under
ui/heroes/assets/(rule 24). Prefer local copies over hotlinked CDN URLs for workbook demos. - Geometric motif heroes can stay CSS-first: circles, small squares, short bars, and rotated lines aligned to a clean grid, with an overlapping badge connecting copy and media columns.
- Glossary — Bézier path: SVG curve via control points; organic blob: freeform filled shape behind media; editorial collage: photo cutouts plus vector doodles; geometric motif kit: a short reusable set of CSS shapes; cutout: background-removed subject layered on the page.
Page layout system and naming
- Name your page-width container. Define one reusable class with
max-width, horizontal centering, and side padding (often fed by--max/--max-width). Compose it onto semantic elements as a second class instead of inventing a new max-width rule per section. - Prefer full-bleed backgrounds on the outer section and constrained content inside the shared container so band colour can run edge to edge.
- Adopt one class-naming convention per page or component set. A useful
default is
block__elementwith optionalblock--modifieroris-stateclasses. Nested chains such asnav__menu__btnappear in the wild but are not strict BEM — prefer flatter element names while learning. - Do not mix flat kebab names,
block__element, and kebab-block__element styles inside the same component without a deliberate reason. - After colour tokens (rules 155–162), you may store spacing, sizing, radius,
breakpoint documentation values, and motion timing as custom properties.
Prefer a small role-based scale. See Chapter 24 and
ui/navbarstokens. - Inline
style="--name: value"may pass per-instance data from HTML into CSS (for example animating a bar towidth: var(--progress)). Prefer that data channel over rewriting selectors for every numeric instance; still avoid inline styles for ordinary colours and spacing.
Document metadata, performance, and UX laws
- Ship a unique
<title>,<meta name="description">, favicon (rel="icon"), and Open Graph / Twitter card tags on pages meant for the public web. Addrel="canonical"when URL variants or mirrors exist. - Aim for Core Web Vitals budgets: INP ≤ 200 ms (p75), LCP ≤ 2.5 s,
CLS ≤ 0.1. Long tasks over 50 ms harm INP — keep handlers short or
yield. Intrinsic
width/heighton images (already taught) protect CLS. Keep shipped JS toward ≤ 300–400 KB gzipped for interactive pages; this workbook stays far under that by design (no framework/bundler required). - Prefer WebP/AVIF where practical, responsive
srcset/sizesor<picture>, andloading="lazy"on below-the-fold images. Keep the LCP image eager. - Consistent Help (WCAG 2.2 SC 3.2.6): if the site offers help (docs, contact, chat), present that mechanism in the same relative order on every page that includes it.
- Miller's Law: keep a primary navigation group near ~7 items (about 5 minimum comfort, 9 absolute max). Chunk denser menus into labelled groups rather than one flat list of dozens.
- Honour
prefers-color-schemeby overriding role tokens inside@media (prefers-color-scheme: dark)(and light if needed). Re-check contrast after each palette. A JS theme switcher is optional later — the media query is enough for system preference. - Use container queries for component layout: set
container-type: inline-sizeon the parent, then@containerwidth rules on descendants. Keep viewport@mediafor page chrome.
Container queries
- Name a container with
container-name(or thecontainershorthand) when more than one containment context nests on the page. - Default the component to a usable narrow layout; enhance inside
@containerwhen the parent is wide enough — same mobile-first habit as Chapter 11, scoped to the component.
UX laws (implementable vocabulary)
- Fitts' Law: make primary targets large enough and spaced — the UX rationale behind the ≥24×24 px floor (comfortable ≈44×44 px).
- Law of Proximity: use
gapand margin rhythm so related controls and copy sit closer to each other than to unrelated neighbours. - Law of Common Region: shared background, border, or
fieldsetmarks a group; do not rely on proximity alone when the region must be obvious. - Von Restorff (isolation) effect: break visual uniformity for the one primary CTA — weight, colour, or size — so it is not one peer among many equal buttons.
- Serial Position Effect: put the most important items first and last in nav lists, step flows, and option groups; middling items are easier to miss.
- Jakob's Law: prefer familiar patterns for carts, menus, forms, and auth — match common affordances unless a clear product reason says otherwise.
- Goal-Gradient / Zeigarnik: show progress (steps, meters, or status text) in multi-step flows so unfinished work stays visible and motivating.