A README is the first thing anyone sees when they land on your repository. Before they read a single line of code, they're judging your project by how that page looks. A wall of plain text with no structure says "thrown together." A clean layout with badges, a table of contents, and properly formatted code blocks says "this person knows what they're doing."
The frustrating part is that most developers already know basic Markdown, bold text, bullet points, a header here and there, but stop short of the techniques that actually make a README look professional. This post covers five specific things that consistently separate good READMEs from forgettable ones, along with the syntax mistakes that quietly break formatting without throwing any kind of error.
1. Collapsible Sections for Long Content
Long installation steps, changelogs, or troubleshooting sections make a README feel bloated, even when the content itself is useful. GitHub supports HTML inside Markdown files, which means you can wrap sections in a native collapsible toggle without any JavaScript.
<details>
<summary>Click to expand installation steps</summary>
1. Clone the repository
2. Run `npm install`
3. Copy `.env.example` to `.env`
4. Run `npm run dev`
</details>
This renders as a small arrow with a label. Clicking it expands the content underneath. It's the same <details> and <summary> tags from regular HTML, and GitHub renders them correctly inside README files without any extra setup.
The detail people usually miss is the blank line right after <summary>. Without that blank line, GitHub sometimes fails to parse the Markdown list or formatting inside the collapsible block correctly, and it just shows up as plain unformatted text. Leave the gap, and everything inside renders normally.
Use this for anything that's useful but not essential reading on first glance, full changelogs, detailed configuration options, or long troubleshooting steps. It keeps the page short for someone skimming, while keeping the depth available for someone who actually needs it.
2. A Real Table of Contents with Working Anchor Links
Once a README passes a few hundred lines, scrolling to find a section becomes annoying. A table of contents at the top fixes this, but it only works if the links actually jump to the right place, which depends on understanding how GitHub generates anchor links from headers.
GitHub automatically converts every header into a lowercase, hyphenated anchor. A header like this:
## Getting Started
becomes a link target of #getting-started. Spaces turn into hyphens, capital letters become lowercase, and most punctuation just gets stripped out entirely.
## Table of Contents
- [Installation](#installation)
- [Configuration](#configuration)
- [API Reference](#api-reference)
- [Troubleshooting](#troubleshooting)
The mistake that breaks this most often is punctuation in headers. A header like ## Configuration (Advanced) doesn't become #configuration-(advanced), the parentheses get removed entirely, so the real anchor is #configuration-advanced. If your table of contents link includes the parentheses, the click does nothing. Strip special characters from your anchor links and you'll save yourself the confusion of a "broken" link that isn't actually broken, it's just pointing at text that doesn't exist.
3. Syntax Highlighted Code Blocks (Not Just Code Blocks)
Almost everyone knows to wrap code in triple backticks. Far fewer people specify the language right after the opening backticks, which is the difference between a gray box of plain text and properly colored, readable syntax.
```javascript
function greet(name) {
return `Hello, ${name}`;
}
```
That word right after the first three backticks, javascript in this case, tells GitHub which syntax highlighter to apply. Leave it off, and you get a plain monospace block with no color at all. It's a one word addition that makes every code sample in your README significantly easier to actually read.
This works for far more than the obvious languages. bash for terminal commands, json for config files, diff for showing before and after changes, yaml for CI configuration, all of them get properly highlighted the moment you name them.
```diff
- const PORT = 3000;
+ const PORT = process.env.PORT || 3000;
```
The diff language tag specifically deserves more use than it gets. It automatically colors lines starting with - in red and lines starting with + in green, which makes before-and-after code comparisons in your documentation instantly easier to scan than a plain paragraph explaining the same change in words.
4. Badges That Actually Communicate Something Useful
Badges (those small colored labels showing build status, version number, or license type) have become a visual signature of professional repositories. They're built using Shields.io and embedded as simple Markdown image links.



The mistake worth avoiding here is treating badges as decoration. A README with eight badges crammed across the top, half of them unrelated to anything the visitor actually cares about, looks busier without communicating more. Pick badges that answer questions a visitor genuinely has: is this actively maintained, what license governs it, does the test suite currently pass, what version am I about to install. Three or four meaningful badges beat eight decorative ones every time.
For a project hosted on npm, the version and download count badges matter. For an open source library, the build status and license badges matter more. Match the badges to what someone evaluating your project would actually want to know before they decide whether to use it.
5. Tables for Anything Comparative
Markdown tables get skipped constantly, usually because the syntax looks more intimidating than it is. But for anything involving options, parameters, or comparisons, a table communicates in seconds what a paragraph would take several sentences to explain, and explain less clearly.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `port` | number | `3000` | Port the server listens on |
| `debug` | boolean | `false` | Enables verbose logging |
| `timeout` | number | `5000` | Request timeout in milliseconds |
The pipe characters define columns, and the second row (the one made of dashes) tells Markdown where the header ends and the data begins. You don't need to align the pipe characters into neat visual columns in your source file for this to render correctly, GitHub handles the spacing automatically regardless of how messy the raw text looks.
This is the single best format for documenting configuration options, API parameters, environment variables, or comparing different versions of something. Anywhere you'd naturally reach for a spreadsheet mentally, reach for a Markdown table in your README instead.
A Quick Note on What Not to Overdo
It's worth saying directly: more Markdown formatting isn't automatically better. A README with collapsible sections nested three levels deep, six badge rows, and tables for things that didn't need tables becomes its own kind of mess. The goal of every technique above is making information easier to find and faster to scan, not showing off every formatting trick Markdown supports.
If you're unsure whether a section needs a particular formatting treatment, ask what a new visitor actually needs in the first 30 seconds on the page: what the project does, how to install it, and where to find more detail if they need it. Everything else can live further down, or behind a collapsible toggle.
The Bottom Line
Good Markdown formatting in a README isn't about knowing obscure syntax tricks, it's about using a handful of techniques (collapsible sections, working anchor links, language tagged code blocks, purposeful badges, and tables) consistently and correctly. Each one is small on its own. Together, they're the difference between a README that looks maintained and one that looks abandoned, regardless of how good the actual code underneath happens to be.