A Complete Guide to Writing on the Web
This post exists to test and demonstrate every kind of content element this site can render. If it looks good here, it will look good everywhere.
Headings
The heading hierarchy gives structure to a document. Use them in order, without skipping levels.
Third-level heading
A third-level heading is useful for sub-sections within an h2 section.
Paragraphs and inline formatting
Regular prose uses bold for emphasis, italics for nuance, and inline code for technical terms. You can also combine them: const x = 1 works fine.
Links look like this one to GitHub — understated, with a soft underline. They should never shout.
Unordered lists
A good unordered list is flat and scannable:
- Local-first data model — your files, not a server
- Instant full-text search across all notes
- Plain markdown, readable in any editor
- No account required, no subscription, no tracking
- Works offline, always
Nested lists are possible but should be used sparingly:
- Engineering
- Frontend: React, TypeScript, CSS
- Backend: Node.js, Go, SQLite
- Design
- Figma for UI work
- Framer for prototyping
Ordered lists
Use numbered lists when sequence or priority matters:
- Write a first draft without editing
- Step away for at least an hour
- Re-read it as a stranger would
- Cut everything that doesn't earn its place
- Read it aloud — if it sounds wrong, it is
Code blocks
Code blocks use syntax highlighting. The language is specified after the opening fence.
JavaScript
// A simple debounce implementation
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const onResize = debounce(() => {
console.log('Window resized');
}, 200);
window.addEventListener('resize', onResize);TypeScript
interface Post {
slug: string;
title: string;
date: string;
tags: string[];
}
async function getPost(slug: string): Promise<Post | null> {
const res = await fetch(`/api/posts/${slug}`);
if (!res.ok) return null;
return res.json();
}CSS
/* Clean card component */
.card {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 20px 24px;
transition: box-shadow 0.2s ease;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}Shell
# Install dependencies
npm install
# Start the dev server
npm run dev
# Build for production
npm run build && npm run startWith a filename title
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const dir = path.join(process.cwd(), 'content/blog');
export function getAllPosts() {
return fs.readdirSync(dir)
.filter(f => f.endsWith('.md'))
.map(f => {
const raw = fs.readFileSync(path.join(dir, f), 'utf8');
const { data, content } = matter(raw);
return { slug: f.replace('.md', ''), ...data, content };
});
}Tables
Tables work best for structured comparisons. Keep columns to a minimum.
Framework comparison
| Framework | Language | Rendering | Best for |
|---|---|---|---|
| Next.js | TypeScript | SSR / SSG / ISR | Full-stack apps |
| Astro | TypeScript | Static + islands | Content sites |
| SvelteKit | TypeScript | SSR / SSG | Apps, sites |
| Remix | TypeScript | SSR | Data-heavy apps |
| Nuxt | TypeScript | SSR / SSG | Vue ecosystem |
CSS unit quick reference
| Unit | Relative to | Use case |
|---|---|---|
px | Screen pixel | Borders, shadows |
rem | Root font size | Font sizes, spacing |
em | Parent font size | Component-local scaling |
% | Parent element | Widths, fluid layout |
vw / vh | Viewport | Full-screen sections |
ch | Character width | Prose line-length |
Blockquotes
A blockquote is for ideas that deserve to stand apart from the prose.
The best interfaces are the ones that disappear. They do not impose themselves — they serve, and then get out of the way.
They can also attribute a source when needed:
Simplicity is not the absence of clutter; simplicity is somehow being essential — understanding the true essence of every element.
— Jonathan Ive
Images
Images come in a few flavours. Here is what each looks like.
Full-width
A standard block image stretches to the content column width, with a border and rounded corners.
Full-width images work well for establishing shots and landscapes.
Smaller / half-width
You can constrain an image by wrapping it in a <div> with a max-width. This is useful for screenshots or UI mockups that look better at a smaller size.
Half-width image — good for portraits, cropped screenshots, or anything where full-width feels too large.
Two images side by side
Side-by-side layout for before/after comparisons or complementary visuals.
Inline image
You can embed a small image inline within a paragraph — useful for icons or badges. Here is an example: this sentence has an inline image right in the middle of the text, and the line keeps flowing normally afterward.
Horizontal rule
A horizontal rule divides major document sections:
Inline code in context
When referencing code inline, use backticks: Array.prototype.map, useEffect, border-radius: 8px. The font switches to monospace automatically and the contrast keeps it readable.
Mixed content
A real post combines all of these. Here is what a technical explanation might look like in practice.
The debounce function above works by resetting a timer every time it is called. Only when the timer completes — after delay milliseconds of silence — does it call the original function.
The pattern in three steps:
- Call the debounced function
- If called again before
delayms, reset the clock - When the clock runs out, fire the original function exactly once
This is useful for:
- Search inputs that fire on every keystroke
- Window resize handlers
- Auto-save logic in editors
The key insight: you are not slowing things down, you are batching rapid requests into a single meaningful one.