diff --git a/.tidyrc b/.tidyrc
new file mode 100644
index 0000000000000000000000000000000000000000..91b95b0fa25f155b36b7d455800de4c7f759c8f6
--- /dev/null
+++ b/.tidyrc
@@ -0,0 +1,17 @@
+# -*- mode: conf-colon; -*-
+char-encoding: utf8
+doctype: html5
+enclose-text: yes
+indent-spaces: 2
+indent: yes
+input-encoding: utf8
+logical-emphasis: yes
+newline: lf
+output-encoding: utf8
+output-html: yes
+show-body-only: yes
+tab-size: 2
+tidy-mark: no
+uppercase-tags: no
+vertical-space: yes
+wrap: 0
diff --git a/Makefile b/Makefile
index 8ff35b4e2b3f6228b8281c6cebf1714d10f30cb6..d32ccfba7a9851330b6d0462cbf827278012e299 100644
--- a/Makefile
+++ b/Makefile
@@ -15,13 +15,19 @@ OUT_MARKUP := $(SRC_MARKUP:$(SRC)/%.md=$(OUT)/%.php)
 OUT_OTHERS := $(SRC_OTHERS:$(SRC)/%=$(OUT)/%)
 OUT_DIRS   := $(SRC_DIRS:$(SRC)/%=$(OUT)/%)
 
-.PHONY: build publish clean
+.PHONY: build validate publish clean
 .DEFAULT_GOAL := build
 
 # Main entry point targets
 
 build : $(OUT_MARKUP) $(OUT_OTHERS) ## Generate publishable files, converting *.md to *.php
 
+validate : ## Validate HTML-like files
+	for f in $(OUT)/*.php; do \
+		echo "$$f" ;\
+		tidy -config .tidyrc -errors -quiet "$$f" ;\
+	done
+
 publish : | $(OUT)/.git build ## Build then push to the 'public' branch if there are changes
 	if [ -n "$$(git -C $(OUT) status --porcelain)" ]; then \
 		cd $(OUT) ;\
@@ -59,7 +65,11 @@ $(OUT)/% : $(SRC)/% | $$(@D)
 	cp $< $@
 
 $(OUT)/%.php : $(SRC)/%.md signature.html | $$(@D)
-	pandoc --output $@ --template signature --from markdown --to html $<
+	pandoc --template signature --from markdown --to html $< \
+	| tidy -config .tidyrc -quiet -output $@
+
+$(OUT)/%.tidy : $(OUT)/%.php
+	tidy -config .tidyrc -quiet -file $@ $<
 
 # Some shell magic to auto-document the main targets. To have a target appear in
 # the output, add a short, one-line comment with a double ## on the same line as