HTML/CSS Guide
Applies to: HTML5, CSS3, SCSS/LESS, Responsive Design, WCAG 2.1 AA
Core Principles
- Semantic First: Use HTML elements for their meaning, not their appearance; style with CSS
- Accessible by Default: Every page must be navigable by keyboard, screen reader, and assistive technology
- Progressive Enhancement: Build a solid HTML foundation, then layer on CSS and JavaScript
- Mobile First: Design for the smallest viewport, then enhance with
min-width media queries
- Performance Budget: Minimize render-blocking resources; prefer system fonts, modern image formats, and critical CSS inlining
Guardrails
Semantic HTML
- Use
<main> once per page; <header>, <footer>, <nav>, <aside> for landmarks
- Use
<article> for self-contained content; <section> for thematic groups with a heading
- Use
<figure>/<figcaption> for captioned media; <time datetime="..."> for dates
- Headings (
<h1>-<h6>) must follow a logical outline; never skip levels for styling
- Use
<button> for actions and <a> for navigation; never <div onclick>
- Lists (
<ul>, <ol>, <dl>) for groups of related items; not <div> sequences
<article>
<header>
<h2>Deploying with Zero Downtime</h2>
<time datetime="2025-03-15">March 15, 2025</time>
</header>
<section aria-labelledby="prereqs">
<h3 id="prereqs">Prerequisites</h3>
<ul>
<li>Container runtime (Docker or Podman)</li>
<li>Load balancer with health checks</li>
</ul>
</section>
</article>
Accessibility (WCAG 2.1 AA)
- Every
<img> must have alt; decorative images use alt=""
- All form inputs must have associated
<label> elements (use for/id)
- Color contrast: 4.5:1 for normal text, 3:1 for large text (18px+ bold or 24px+)
- Focus indicators must be visible; never
outline: none without a replacement
- Skip-to-content link as the first focusable element on every page
- All interactive elements must be keyboard-operable
- Use
aria-live="polite" for dynamic updates; role="alert" for immediate announcements
- Use
aria-expanded, aria-controls, aria-haspopup for disclosure widgets
- Do not rely on color alone to convey information (add icons, text, or patterns)
<a href="#main" class="skip-link">Skip to main content</a>
<form aria-labelledby="signup-heading">
<h2 id="signup-heading">Create Account</h2>
<label for="email">Email</label>
<input id="email" type="email" required autocomplete="email"
aria-describedby="email-hint" />
<p id="email-hint" class="hint">We will never share your email.</p>
<button type="submit">Create Account</button>
</form>
CSS Architecture
- One component per file; file name matches component (
card.css, nav.css)
- Use
@layer for specificity control: reset, base, layout, components, utilities
- Prefer BEM (
.block__element--modifier) or utility-first -- pick one, stay consistent
- Never use
!important outside utility overrides; fix specificity instead
- Max 3 levels of nesting (native or preprocessor)
- Custom properties for all theme values (colors, spacing, typography, radii)
- No inline styles unless dynamically computed by JavaScript
@layer reset, base, layout, components, utilities;
@layer base {
:root {
--color-primary: oklch(55% 0.22 265);
--color-surface: oklch(98% 0.005 265);
--color-text: oklch(20% 0.02 265);
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 2rem;
--radius-md: 0.5rem;
--font-body: system-ui, -apple-system, sans-serif;
}
}
@layer components {
.card { background: var(--color-surface); border-radius: var(--radius-md); padding: var(--space-md); }
.card__title { font-size: 1.25rem; color: var(--color-text); }
.card--featured { border: 2px solid var(--color-primary); }
}
Performance
- Inline critical CSS in
<head> for first contentful paint
- Use
font-display: swap for web fonts; prefer system font stacks
- Modern image formats: AVIF > WebP > JPEG; use
<picture> with fallbacks
- Animate only
transform and opacity to avoid layout reflows
- Use
content-visibility: auto for off-screen sections
- Set
width/height or aspect-ratio on media to prevent layout shift
<picture>
<source srcset="hero.avif" type="image/avif" />
<source srcset="hero.webp" type="image/webp" />
<img src="hero.jpg" alt="Dashboard overview" width="1200" height="630"
loading="lazy" decoding="async" />
</picture>
Responsive Design
- Mobile-first: base styles for small screens, add complexity with
min-width queries
- Use
rem/em for sizing; reserve px for borders and fine details only
- Fluid typography with
clamp(): font-size: clamp(1rem, 0.5rem + 1.5vw, 1.5rem)
- Container queries (
@container) for component-level responsiveness
- Test at 320px, 768px, 1024px, 1440px, and with 200% browser zoom
- Touch targets: at least 44x44px on mobile
Key Patterns
Grid (2D) vs Flexbox (1D)
Use CSS Grid for page layouts with rows and columns; use Flexbox for single-axis alignment.
/* Grid: 2D page layout */
.page {
display: grid;
grid-template: "header header" auto "sidebar main" 1fr "footer footer" auto / 16rem 1fr;
min-height: 100dvh;
}
/* Flexbox: 1D navigation */
.nav { display: flex; align-items: center; gap: var(--space-md); }
.nav__logo { margin-right: auto; }
Container Queries
.card-container { container-type: inline-size; container-name: card; }
@container card (min-width: 30rem) {
.card { flex-direction: row; align-items: center; }
}
The :has() Selector
.field:has(input:focus) { outline: 2px solid var(--color-primary); }
.card:has(img) { padding: 0; }
form:has(:invalid) button[type="submit"] { opacity: 0.5; pointer-events: none; }
Cascade Layers (@layer)
@layer reset, base, layout, components, utilities;
@layer reset { *, *::before, *::after { box-sizing: border-box; margin: 0; } }
@layer utilities {
.visually-hidden {
clip: rect(0 0 0 0); clip-path: inset(50%); height: 1px;
overflow: hidden; position: absolute; white-space: nowrap; width: 1px;
}
}
Dark Mode and User Preferences
:root { color-scheme: light dark; --color-bg: oklch(98% 0.005 265); }
@media (prefers-color-scheme: dark) { :root { --color-bg: oklch(15% 0.02 265); } }
[data-theme="dark"] { --color-bg: oklch(15% 0.02 265); }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important; transition-duration: 0.01ms !important;
}
}
Accessibility Checklist (WCAG 2.1 AA)
Perceivable
Operable
Understandable
Robust
Tooling
npx stylelint "**/*.css" # CSS lint
npx stylelint "**/*.css" --fix # CSS lint auto-fix
npx htmlhint "**/*.html" # HTML validation
npx prettier --check "**/*.{html,css,scss}" # Format check
npx prettier --write "**/*.{html,css,scss}" # Format fix
npx lighthouse http://localhost:3000 --output html --view # Audit
npx @axe-core/cli http://localhost:3000 # Accessibility check
npx pa11y http://localhost:3000 # Accessibility test
Stylelint Configuration
{
"extends": ["stylelint-config-standard"],
"rules": {
"declaration-no-important": true,
"selector-max-specificity": "0,3,0",
"max-nesting-depth": 3,
"color-function-notation": "modern",
"selector-class-pattern": "^[a-z][a-z0-9]*(__[a-z0-9-]+)?(--[a-z0-9-]+)?$"
}
}
References
For detailed layout examples, accessibility patterns, and modern CSS features, see:
- references/patterns.md -- Grid/Flexbox layouts, modal dialogs, fluid typography, scroll animations
External References
1---2name: html-css-guide3description: HTML and CSS guardrails, patterns, and best practices for AI-assisted development. Use when working with HTML/CSS files (.html, .css, .scss, .less), or when the user mentions HTML/CSS. Provides semantic HTML guidelines, accessibility standards, modern CSS patterns, and responsive design conventions specific to this project's coding standards.4license: MIT5---6
7# HTML/CSS Guide
8
9> Applies to: HTML5, CSS3, SCSS/LESS, Responsive Design, WCAG 2.1 AA
10
11## Core Principles
12
131. **Semantic First**: Use HTML elements for their meaning, not their appearance; style with CSS
142. **Accessible by Default**: Every page must be navigable by keyboard, screen reader, and assistive technology
153. **Progressive Enhancement**: Build a solid HTML foundation, then layer on CSS and JavaScript
164. **Mobile First**: Design for the smallest viewport, then enhance with `min-width` media queries
175. **Performance Budget**: Minimize render-blocking resources; prefer system fonts, modern image formats, and critical CSS inlining
18
19## Guardrails
20
21### Semantic HTML
22
23- Use `<main>` once per page; `<header>`, `<footer>`, `<nav>`, `<aside>` for landmarks
24- Use `<article>` for self-contained content; `<section>` for thematic groups with a heading
25- Use `<figure>`/`<figcaption>` for captioned media; `<time datetime="...">` for dates
26- Headings (`<h1>`-`<h6>`) must follow a logical outline; never skip levels for styling
27- Use `<button>` for actions and `<a>` for navigation; never `<div onclick>`
28- Lists (`<ul>`, `<ol>`, `<dl>`) for groups of related items; not `<div>` sequences
29
30```html
31<article>
32 <header>
33 <h2>Deploying with Zero Downtime</h2>
34 <time datetime="2025-03-15">March 15, 2025</time>
35 </header>
36 <section aria-labelledby="prereqs">
37 <h3 id="prereqs">Prerequisites</h3>
38 <ul>
39 <li>Container runtime (Docker or Podman)</li>
40 <li>Load balancer with health checks</li>
41 </ul>
42 </section>
43</article>
44```
45
46### Accessibility (WCAG 2.1 AA)
47
48- Every `<img>` must have `alt`; decorative images use `alt=""`
49- All form inputs must have associated `<label>` elements (use `for`/`id`)
50- Color contrast: 4.5:1 for normal text, 3:1 for large text (18px+ bold or 24px+)
51- Focus indicators must be visible; never `outline: none` without a replacement
52- Skip-to-content link as the first focusable element on every page
53- All interactive elements must be keyboard-operable
54- Use `aria-live="polite"` for dynamic updates; `role="alert"` for immediate announcements
55- Use `aria-expanded`, `aria-controls`, `aria-haspopup` for disclosure widgets
56- Do not rely on color alone to convey information (add icons, text, or patterns)
57
58```html
59<a href="#main" class="skip-link">Skip to main content</a>
60
61<form aria-labelledby="signup-heading">
62 <h2 id="signup-heading">Create Account</h2>
63 <label for="email">Email</label>
64 <input id="email" type="email" required autocomplete="email"
65 aria-describedby="email-hint" />
66 <p id="email-hint" class="hint">We will never share your email.</p>
67 <button type="submit">Create Account</button>
68</form>
69```
70
71### CSS Architecture
72
73- One component per file; file name matches component (`card.css`, `nav.css`)
74- Use `@layer` for specificity control: reset, base, layout, components, utilities
75- Prefer BEM (`.block__element--modifier`) or utility-first -- pick one, stay consistent
76- Never use `!important` outside utility overrides; fix specificity instead
77- Max 3 levels of nesting (native or preprocessor)
78- Custom properties for all theme values (colors, spacing, typography, radii)
79- No inline styles unless dynamically computed by JavaScript
80
81```css
82@layer reset, base, layout, components, utilities;
83
84@layer base {
85 :root {
86 --color-primary: oklch(55% 0.22 265);
87 --color-surface: oklch(98% 0.005 265);
88 --color-text: oklch(20% 0.02 265);
89 --space-sm: 0.5rem;
90 --space-md: 1rem;
91 --space-lg: 2rem;
92 --radius-md: 0.5rem;
93 --font-body: system-ui, -apple-system, sans-serif;
94 }
95}
96
97@layer components {
98 .card { background: var(--color-surface); border-radius: var(--radius-md); padding: var(--space-md); }
99 .card__title { font-size: 1.25rem; color: var(--color-text); }
100 .card--featured { border: 2px solid var(--color-primary); }
101}
102```
103
104### Performance
105
106- Inline critical CSS in `<head>` for first contentful paint
107- Use `font-display: swap` for web fonts; prefer system font stacks
108- Modern image formats: AVIF > WebP > JPEG; use `<picture>` with fallbacks
109- Animate only `transform` and `opacity` to avoid layout reflows
110- Use `content-visibility: auto` for off-screen sections
111- Set `width`/`height` or `aspect-ratio` on media to prevent layout shift
112
113```html
114<picture>
115 <source srcset="hero.avif" type="image/avif" />
116 <source srcset="hero.webp" type="image/webp" />
117 <img src="hero.jpg" alt="Dashboard overview" width="1200" height="630"
118 loading="lazy" decoding="async" />
119</picture>
120```
121
122### Responsive Design
123
124- Mobile-first: base styles for small screens, add complexity with `min-width` queries
125- Use `rem`/`em` for sizing; reserve `px` for borders and fine details only
126- Fluid typography with `clamp()`: `font-size: clamp(1rem, 0.5rem + 1.5vw, 1.5rem)`
127- Container queries (`@container`) for component-level responsiveness
128- Test at 320px, 768px, 1024px, 1440px, and with 200% browser zoom
129- Touch targets: at least 44x44px on mobile
130
131## Key Patterns
132
133### Grid (2D) vs Flexbox (1D)
134
135Use CSS Grid for page layouts with rows and columns; use Flexbox for single-axis alignment.
136
137```css
138/* Grid: 2D page layout */
139.page {
140 display: grid;
141 grid-template: "header header" auto "sidebar main" 1fr "footer footer" auto / 16rem 1fr;
142 min-height: 100dvh;
143}
144
145/* Flexbox: 1D navigation */
146.nav { display: flex; align-items: center; gap: var(--space-md); }
147.nav__logo { margin-right: auto; }
148```
149
150### Container Queries
151
152```css
153.card-container { container-type: inline-size; container-name: card; }
154
155@container card (min-width: 30rem) {
156 .card { flex-direction: row; align-items: center; }
157}
158```
159
160### The :has() Selector
161
162```css
163.field:has(input:focus) { outline: 2px solid var(--color-primary); }
164.card:has(img) { padding: 0; }
165form:has(:invalid) button[type="submit"] { opacity: 0.5; pointer-events: none; }
166```
167
168### Cascade Layers (@layer)
169
170```css
171@layer reset, base, layout, components, utilities;
172
173@layer reset { *, *::before, *::after { box-sizing: border-box; margin: 0; } }
174@layer utilities {
175 .visually-hidden {
176 clip: rect(0 0 0 0); clip-path: inset(50%); height: 1px;
177 overflow: hidden; position: absolute; white-space: nowrap; width: 1px;
178 }
179}
180```
181
182### Dark Mode and User Preferences
183
184```css
185:root { color-scheme: light dark; --color-bg: oklch(98% 0.005 265); }
186@media (prefers-color-scheme: dark) { :root { --color-bg: oklch(15% 0.02 265); } }
187[data-theme="dark"] { --color-bg: oklch(15% 0.02 265); }
188
189@media (prefers-reduced-motion: reduce) {
190 *, *::before, *::after {
191 animation-duration: 0.01ms !important; transition-duration: 0.01ms !important;
192 }
193}
194```
195
196## Accessibility Checklist (WCAG 2.1 AA)
197
198**Perceivable**
199- [ ] All images have descriptive `alt` (or `alt=""` for decorative)
200- [ ] Color contrast: 4.5:1 body text, 3:1 large text
201- [ ] Information not conveyed by color alone
202- [ ] Text resizable to 200% without content loss
203- [ ] Captions for video; transcripts for audio
204
205**Operable**
206- [ ] All functionality keyboard-accessible (Tab, Enter, Escape, arrows)
207- [ ] Logical focus order; visible focus indicators
208- [ ] No keyboard traps; modals return focus on close
209- [ ] Skip navigation link as first focusable element
210- [ ] Touch targets at least 44x44px
211
212**Understandable**
213- [ ] `<html lang="...">` set correctly
214- [ ] Form inputs have visible labels and descriptive error messages
215- [ ] Consistent navigation across pages
216
217**Robust**
218- [ ] Valid HTML (W3C validator)
219- [ ] ARIA roles, states, and properties match widget behavior
220- [ ] Tested with screen reader (VoiceOver, NVDA, or JAWS)
221- [ ] Tested with 200% browser zoom
222
223## Tooling
224
225```bash
226npx stylelint "**/*.css" # CSS lint
227npx stylelint "**/*.css" --fix # CSS lint auto-fix
228npx htmlhint "**/*.html" # HTML validation
229npx prettier --check "**/*.{html,css,scss}" # Format check
230npx prettier --write "**/*.{html,css,scss}" # Format fix
231npx lighthouse http://localhost:3000 --output html --view # Audit
232npx @axe-core/cli http://localhost:3000 # Accessibility check
233npx pa11y http://localhost:3000 # Accessibility test
234```
235
236### Stylelint Configuration
237
238```json
239{
240 "extends": ["stylelint-config-standard"],
241 "rules": {
242 "declaration-no-important": true,
243 "selector-max-specificity": "0,3,0",
244 "max-nesting-depth": 3,
245 "color-function-notation": "modern",
246 "selector-class-pattern": "^[a-z][a-z0-9]*(__[a-z0-9-]+)?(--[a-z0-9-]+)?$"
247 }
248}
249```
250
251## References
252
253For detailed layout examples, accessibility patterns, and modern CSS features, see:
254
255- [references/patterns.md](references/patterns.md) -- Grid/Flexbox layouts, modal dialogs, fluid typography, scroll animations
256
257## External References
258
259- [MDN HTML Reference](https://developer.mozilla.org/en-US/docs/Web/HTML)
260- [MDN CSS Reference](https://developer.mozilla.org/en-US/docs/Web/CSS)
261- [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/)
262- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
263- [CSS Tricks Guide to Grid](https://css-tricks.com/snippets/css/complete-guide-grid/)
264- [CSS Tricks Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
265- [Every Layout](https://every-layout.dev/)
266- [Modern CSS Solutions](https://moderncss.dev/)
267- [web.dev Learn CSS](https://web.dev/learn/css/)
268- [Can I Use](https://caniuse.com/)