A Working Setup: Formatting, Linting and Live Preview
Consistent formatting, markup linting, live reload and shared settings. A day of setup that pays back on every file afterwards.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
Concept
The difference between a comfortable project and a frustrating one is largely tooling that runs without being asked. Four things are worth setting up once.
1. Automatic formatting
A formatter rewrites files to a consistent style on save. It ends indentation arguments permanently, because nobody is formatting by hand any more.
// .vscode/settings.json - committed, so the whole team shares it
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true
}// .prettierrc
{
"printWidth": 100,
"tabWidth": 2,
"singleQuote": false,
"htmlWhitespaceSensitivity": "css"
}The htmlWhitespaceSensitivity setting matters for HTML specifically. Whitespace between inline elements is rendered as a space, so a formatter that reflows freely can change how a page looks. The css value tells it to respect the display value of each element.
EditorConfig
# .editorconfig - respected by nearly every editor
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = falseWorth adding to every project. It is a single small file and it works across editors, which a formatter configuration alone does not.
2. Markup linting
A formatter makes files look consistent. A linter tells you the markup is wrong.
npm install --save-dev html-validate// .htmlvalidate.json
{
"extends": ["html-validate:recommended"],
"rules": {
"require-sri": "off",
"no-inline-style": "warn"
}
}npx html-validate "src/**/*.html"It catches duplicate ids, bad nesting, missing alt, obsolete attributes and unclosed tags - as you work, rather than at launch.
Accessibility linting
An accessibility linter catches a further class of problem: an anchor with no href, an aria-* attribute on an element that does not support it, a positive tabindex, an image with a redundant alt.
3. Live preview
A local server that reloads the browser on save.
# any of these serve the current folder with live reload
npx live-server
npx browser-sync start --server --files "**/*"
python -m http.server 8000 # no reload, but no install eitherEditor extensions do the same with one click. Whatever you use, serve over HTTP rather than opening the file directly - root relative paths, modules, fetch and several browser APIs behave differently over file://, and every one of those differences is a bug you will otherwise find at deployment.
Testing from a phone
Browser Sync prints a network address alongside the local one. Opening that on a phone on the same network shows the real page on real hardware, with real touch behaviour - which no emulator reproduces.
4. Snippets
Emmet covers general structure. Snippets cover the specific blocks you write repeatedly in this project.
// .vscode/html.code-snippets
{
"Labelled field": {
"prefix": "field",
"body": [
"",
" ",
" ",
"
"
],
"description": "A labelled form field"
},
"Responsive image": {
"prefix": "rimg",
"body": [
"
"
]
}
}$1 and $2 are tab stops; repeating $1 fills both places at once. The pipe syntax offers a dropdown of choices.
Committing snippets to .vscode/ shares them with everyone on the project, which quietly enforces house patterns better than a style document does.
Extensions worth having
| Purpose | What it gives you |
|---|---|
| Formatter | Consistent files with no effort |
| HTML validator | Markup errors as you type |
| Accessibility linter | Missing labels, bad ARIA, positive tabindex |
| Auto rename tag | Closing tag follows the opening one |
| Path autocomplete | Suggests real file paths in src and href |
| Colour highlight | Shows colour values inline |
| Live server | Reload on save |
| Spell checker | Typos in visible text, which readers do notice |
Resist installing thirty. Each one costs startup time and adds a way for the editor to behave unexpectedly.
Committing the configuration
project/
.editorconfig committed
.prettierrc committed
.htmlvalidate.json committed
.vscode/
settings.json committed - shared project settings
extensions.json committed - recommended extensions
html.code-snippets committed - shared snippets
.gitignore// .vscode/extensions.json - prompts new contributors to install these
{
"recommendations": [
"esbenp.prettier-vscode",
"formulahendry.auto-rename-tag",
"ritwickdey.liveserver"
]
}A new contributor clones the repository, opens it, accepts the prompt, and has the same setup as everyone else in two minutes.
Checks in the build
// package.json
{
"scripts": {
"lint:html": "html-validate "src/**/*.html"",
"lint:a11y": "pa11y-ci",
"format:check": "prettier --check "src/**/*.{html,css,js}"",
"check": "npm run format:check && npm run lint:html && npm run lint:a11y"
}
}Run check in continuous integration and a broken page cannot reach the main branch. This is what stops quality decaying over months, which manual discipline reliably fails to do.
Important rules
- Serve over HTTP, not
file://. - HTML whitespace is significant between inline elements; configure the formatter accordingly.
- Commit configuration so everyone shares it.
- A formatter is not a linter. You want both.
- Automated checks in CI are what make the standard stick.
Common mistakes
- Opening files directly and hitting path problems only at deployment.
- Formatting by hand.
- Configuration kept locally, so every contributor formats differently.
- A formatter that reflows inline elements and changes spacing on the page.
- Thirty extensions and a slow editor.
- No checks in the build, so standards slip silently.
Best practices
- Add
.editorconfigto every project. - Turn on format on save immediately.
- Add a markup validator and an accessibility linter.
- Use a live server, and test on a phone over the network address.
- Write snippets for repeated project patterns and commit them.
- Run format, validate and accessibility checks in CI.
Practice
- Set up a project with
.editorconfig, a formatter and format on save. - Add a markup validator and fix everything it reports on one page.
- Start a live server and open the page on a phone using the network address.
- Write a snippet for a pattern you type often and use it five times.