technical writing
by @royhk920
Activate this skill whenever a user asks to write, structure, or improve developer documentation. Covers README templates, API documentation (OpenAPI, JSDoc,...
clawhub install technical-writingπ About This Skill
name: technical-writing description: > Activate this skill whenever a user asks to write, structure, or improve developer documentation. Covers README templates, API documentation (OpenAPI, JSDoc, TSDoc), Architecture Decision Records, RFCs, changelogs, runbooks, diagram-as-code (Mermaid, D2, PlantUML), technical writing style guides, documentation site generators (Docusaurus, Starlight, MkDocs, VitePress), docs-as-code CI workflows, and code example best practices. Provides complete templates and a common pitfalls reference. license: MIT compatibility: "openclaw" metadata: version: "1.0.0" author: "OpenClaw" category: "docs"
Technical Writing for Developers
Write clear, maintainable developer documentation β from README files to full documentation sites.
1. Documentation Strategy
1.1 Diataxis framework
Organize documentation into four distinct types:
PRACTICAL THEORETICAL
(steps to follow) (understanding)
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββ
LEARNING β Tutorials β Explanation β
(study) β "Learning-oriented"β "Understanding-oriented"β
β Hand-held lessons β Background, context β
βββββββββββββββββββββββΌββββββββββββββββββββββββββ€
WORKING β How-to Guides β Reference β
(apply) β "Task-oriented" β "Information-oriented" β
β Solve a problem β Dry, accurate, complete β
βββββββββββββββββββββββ΄ββββββββββββββββββββββββββ
| Type | Goal | Example | |------|------|---------| | Tutorial | Teach a beginner by doing | "Build your first API in 10 minutes" | | How-to Guide | Solve a specific problem | "How to add authentication" | | Reference | Provide technical facts | API endpoint docs, config options | | Explanation | Provide context and reasoning | "Why we chose PostgreSQL over MongoDB" |
1.2 Audience analysis
Before writing, answer:
1. Who is reading? (Beginner dev, senior engineer, DevOps, manager) 2. What do they know already? (Prerequisites, assumed knowledge) 3. Why are they reading? (Learn, solve problem, look up fact) 4. Where will they read? (IDE, phone, printed, terminal)
1.3 Documentation lifecycle
Create β Review (PR) β Publish β Maintain β Deprecate β Archive
β β
ββββββββββββββββββββββ
(update cycle)
2. README Templates
2.1 Library / package README
# library-name
> One-line description of what this library does.
  
Features
Installation
bash npm install library-name
Quick Start
ts
import { something } from "library-name";const result = something({ option: true }); console.log(result);
Usage
Basic usage
ts
// Minimal example showing the most common use case
Advanced usage
ts
// Example showing configuration options, edge cases, or composition
ts const result = functionName({ option1: "value" });functionName(options)API Reference
option1| Parameter | Type | Default | Description | |-----------|------|---------|-------------| |
|string| β | Required. Description. | |option2|boolean|false| Optional. Description. |ResultTypeReturns:
β Description of return value.Example:
debugConfiguration
| Option | Type | Default | Description | |--------|------|---------|-------------| |
|boolean|false| Enable debug logging | |timeout|number|5000| Request timeout in ms |Contributing
See CONTRIBUTING.md for development setup and guidelines.
License
2.2 CLI tool README
# my-cli
> One-line description of what the CLI does.
Installation
bash
npm
npm install -g my-cliHomebrew
brew install my-cliBinary
curl -fsSL https://get.my-cli.dev | sh
Quick Start
bash
Initialize a new project
my-cli init my-projectRun the main command
my-cli run --config config.ymlbash my-cli init my-project --template minimalmy-cli init [name]Commands
Create a new project.
bash my-cli run --config custom.yml --verbose| Flag | Short | Default | Description | |------|-------|---------|-------------| |--template|-t|default| Project template | |--force|-f|false| Overwrite existing |my-cli runRun the main process.
yaml| Flag | Short | Default | Description | |------|-------|---------|-------------| |--config|-c|config.yml| Config file path | |--verbose|-v|false| Verbose output | |--dry-run| |false| Show what would happen |Configuration File
config.yml
output: dist verbose: false plugins: - name: plugin-a option: valueMY_CLI_CONFIGEnvironment Variables
| Variable | Default | Description | |----------|---------|-------------| |
|config.yml| Config file path | |MY_CLI_LOG_LEVEL|info| Log level |0Exit Codes
| Code | Meaning | |------|---------| |
| Success | |1| General error | |2| Invalid arguments | |3| Config file not found |
2.3 API service README
# My API
> REST API for managing widgets.
Base URL: https://api.example.com/v1
Authentication
All requests require a Bearer token:
bash curl -H "Authorization: Bearer YOUR_TOKEN" https://api.example.com/v1/widgets
Quick Start
bash
Create a widget
curl -X POST https://api.example.com/v1/widgets \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "My Widget", "color": "blue"}'json { "type": "https://api.example.com/errors/not-found", "title": "Not Found", "status": 404, "detail": "Widget with ID 'abc' not found" }GETEndpoints
| Method | Path | Description | |--------|------|-------------| |
|/widgets| List all widgets | |POST|/widgets| Create a widget | |GET|/widgets/:id| Get a widget | |PATCH|/widgets/:id| Update a widget | |DELETE|/widgets/:id| Delete a widget |Error Responses
All errors follow RFC 7807:
Rate Limits
| Plan | Requests/minute |
|------|----------------|
| Free | 60 |
| Pro | 600 |
SDKs
JavaScript
Python Running Locally
bash
git clone https://github.com/org/my-api.git
cd my-api
cp .env.example .env
docker compose up -d
npm run dev
3. API Documentation
3.1 JSDoc / TSDoc patterns
/**
* Fetches a user by their unique identifier.
*
* @param id - The unique user identifier (UUID format)
* @returns The user object, or null if not found
* @throws {AuthenticationError} If the request is not authenticated
* @throws {RateLimitError} If the rate limit is exceeded
*
* @example
* ts
* const user = await getUser("550e8400-e29b-41d4-a716-446655440000");
* if (user) {
* console.log(user.name);
* }
* *
* @see {@link updateUser} for modifying user data
* @since 1.2.0
*/
async function getUser(id: string): Promise {
// ...
}
3.2 Common JSDoc/TSDoc tags
| Tag | Purpose | Example |
|-----|---------|---------|
| @param | Document a parameter | @param name - The user's display name |
| @returns | Document return value | @returns The created resource |
| @throws | Document exceptions | @throws {NotFoundError} If resource missing |
| @example | Provide usage example | Code block with usage |
| @see | Cross-reference | @see {@link OtherFunction} |
| @since | Version introduced | @since 2.0.0 |
| @deprecated | Mark as deprecated | @deprecated Use newFunction instead |
| @default | Default value | @default false |
| @remarks | Additional context | Extended explanation |
| @internal | Not part of public API | Excluded from generated docs |
3.3 Python docstrings (Google style)
def create_order(
user_id: str,
items: list[OrderItem],
*,
discount_code: str | None = None,
) -> Order:
"""Create a new order for the given user. Validates inventory availability, applies any discount codes,
and calculates the final total including tax.
Args:
user_id: The unique identifier of the customer.
items: List of items to include in the order. Must not be empty.
discount_code: Optional promotional discount code.
Returns:
The created Order object with a generated order ID and
calculated totals.
Raises:
ValueError: If items list is empty.
InsufficientStockError: If any item exceeds available inventory.
InvalidDiscountError: If the discount code is expired or invalid.
Example:
>>> order = create_order(
... user_id="user_123",
... items=[OrderItem(sku="WIDGET-1", quantity=2)],
... discount_code="SAVE10",
... )
>>> print(order.total)
18.00
"""
3.4 Generating docs from code
| Tool | Language | Command |
|------|----------|---------|
| typedoc | TypeScript | npx typedoc --entryPoints src/index.ts --out docs |
| sphinx-autodoc | Python | sphinx-apidoc -o docs/api src/ |
| rustdoc | Rust | cargo doc --open |
| godoc | Go | go doc -all ./... |
| javadoc | Java | javadoc -d docs src/**/*.java |
4. Architecture Decision Records (ADRs)
4.1 Why ADRs matter
ADRs capture the why behind architectural decisions. Code shows what; ADRs explain why that approach was chosen over alternatives.
4.2 ADR template
# ADR-001: Use PostgreSQL as primary database
Status
Accepted (2025-01-15)
Context
We need a primary database for our application that handles:
Decision
We will use PostgreSQL 16 as our primary database.
Consequences
Positive
avoids Elasticsearch dependencyNegative
Neutral
Alternatives Considered
MySQL 8
MongoDB
SQLite
4.3 ADR file organization
docs/
adr/
index.md # Table of contents
0001-use-postgresql.md
0002-adopt-event-sourcing.md
0003-migrate-to-typescript.md
template.md # Copy this for new ADRs
4.4 ADR tooling
# adr-tools (shell-based)
brew install adr-tools
adr init docs/adr
adr new "Use PostgreSQL as primary database"
adr listLog4brains (web-based viewer)
npm install -g log4brains
log4brains init
log4brains adr new "Use PostgreSQL"
log4brains preview # Opens browser
5. RFCs (Request for Comments)
5.1 RFC vs ADR
| Aspect | ADR | RFC | |--------|-----|-----| | Scope | Single decision | Larger design proposal | | Length | 1-2 pages | 3-10+ pages | | Process | Lightweight | Formal review period | | Audience | Future maintainers | Current team for input | | Timing | During/after decision | Before implementation |
5.2 RFC template
# RFC: Implement Real-Time Notifications System
Author: Jane Smith Date: 2025-02-01 Status: In Review Reviewers: @backend-team, @frontend-team
Summary
Proposal to add real-time push notifications using WebSocket connections and a Redis Pub/Sub backend, replacing the current polling approach.
Motivation
Current polling-based notifications:
Users have requested instant notifications in feedback surveys (Issue #234).
Detailed Design
Architecture
Client βββ WebSocket βββ WS Server βββ Redis Pub/Sub βββ App Server
json { "id": "evt_abc123", "type": "notification", "data": { "title": "New comment on your post", "body": "John replied to your post...", "url": "/posts/123#comment-456" }, "timestamp": "2025-02-01T12:00:00Z" }wss://ws.example.comConnection Lifecycle
1. Client authenticates via REST API, receives JWT 2. Client opens WebSocket to
with JWT 3. Server validates JWT, subscribes to user's Redis channel 4. App server publishes events to Redis 5. WS server forwards events to connected clientMessage Format
Reconnection Strategy
Exponential backoff: 1s, 2s, 4s, 8s, max 30s
Resume from last received event ID (server-side buffer: 5 min) Drawbacks
Added infrastructure complexity (Redis, WS server)
WebSocket connections consume server memory (~2KB per connection)
Requires new monitoring and alerting Alternatives Considered
Server-Sent Events (SSE)
Simpler but unidirectional. Would work for notifications but limits
future bidirectional features (typing indicators, presence).Long Polling
Lower infrastructure cost but still has latency and unnecessary requests.Third-Party Service (Pusher/Ably)
Lower maintenance but adds vendor dependency and per-message cost
that becomes expensive at scale.Rollout Plan
1. Phase 1 (Week 1-2): Infrastructure setup, basic WS server
2. Phase 2 (Week 3-4): Integration with notification service
3. Phase 3 (Week 5): Frontend integration, A/B test vs polling
4. Phase 4 (Week 6): Full rollout, deprecate polling
Unresolved Questions
Should we support notifications when the browser tab is in the background?
What is the maximum number of concurrent connections we need to support?
Should we persist notification history in a database or only in-memory?
6. Changelogs
6.1 Keep a Changelog format
# Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
[2.1.0] - 2025-02-15
Added
Changed
Fixed
[2.0.0] - 2025-01-01
Added
Changed
Removed
endpoints (use /api/v2/)Security
to fix CVE-2024-XXXXX[Unreleased]: https://github.com/org/repo/compare/v2.1.0...HEAD [2.1.0]: https://github.com/org/repo/compare/v2.0.0...v2.1.0 [2.0.0]: https://github.com/org/repo/releases/tag/v2.0.0
6.2 Changelog categories
| Category | When to use | |----------|-------------| | Added | New features | | Changed | Changes in existing functionality | | Deprecated | Soon-to-be removed features | | Removed | Removed features | | Fixed | Bug fixes | | Security | Vulnerability fixes |
6.3 Automated changelogs
# release-please (Google) β generates changelog from conventional commits
npm install -D release-pleasechangesets (Atlassian) β developer-authored change descriptions
npm install -D @changesets/cli
npx changeset init
npx changeset # Create a changeset
npx changeset version # Update versions + changelog
npx changeset publish # Publish to npm
7. Runbooks and Playbooks
7.1 Incident response runbook template
# Runbook: Database Connection Pool Exhaustion
Severity: P1 (Service degradation) On-call team: Backend Last updated: 2025-02-01
Symptoms
Diagnosis
Step 1: Confirm the issue
bash
Check active connections
psql -c "SELECT count(*) FROM pg_stat_activity WHERE state = 'active';"Check connection limits
psql -c "SHOW max_connections;"Check waiting connections
psql -c "SELECT count(*) FROM pg_stat_activity WHERE wait_event_type = 'Lock';"
Step 2: Identify the cause
bash
Find long-running queries
psql -c "SELECT pid, now() - query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '30 seconds' ORDER BY duration DESC LIMIT 10;"Check for idle-in-transaction connections
psql -c "SELECT count(*), state FROM pg_stat_activity GROUP BY state;"
Step 3: Check application logs
bash
Look for connection errors
journalctl -u my-app --since "1 hour ago" | grep -i "connection\|pool\|timeout"
Remediation
Immediate (stop the bleeding)
bash
Kill idle connections older than 5 minutes
psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction' AND now() - state_change > interval '5 minutes';"Restart application to reset connection pool
sudo systemctl restart my-appmax_connectionsShort-term (prevent recurrence)
1. Increase
in PostgreSQL config 2. Add statement timeout:SET statement_timeout = '30s';3. Configure PgBouncer connection poolingLong-term (root cause fix)
1. Audit all database queries for missing connection releases 2. Add connection pool monitoring to Grafana dashboard 3. Set up alerts for connection count > 80% of max
Escalation
If not resolved within 30 minutes: 1. Page the database team: @db-oncall 2. Consider enabling read-only mode 3. Contact cloud provider support if infrastructure-related
Post-Incident
[ ] Write post-mortem within 48 hours [ ] File tickets for long-term fixes [ ] Update this runbook with lessons learned
7.2 Post-mortem template
# Post-Mortem: [Incident Title]
Date: 2025-02-15 Duration: 45 minutes (14:30 - 15:15 UTC) Severity: P1 Author: Jane Smith
Summary
Brief 2-3 sentence summary of what happened and the impact.
Timeline (UTC)
| Time | Event | |------|-------| | 14:30 | Monitoring alert: HTTP 503 rate > 5% | | 14:32 | On-call engineer acknowledged, began investigation | | 14:38 | Identified database connection pool exhaustion | | 14:42 | Killed idle-in-transaction connections | | 14:45 | Restarted application pods | | 14:50 | Error rate returned to normal | | 15:15 | Incident officially resolved after monitoring period |
Root Cause
A deployed migration added a new query that opened a transaction but did not close it on one error path, causing connections to accumulate.
Impact
What Went Well
What Went Wrong
Action Items
| Action | Owner | Due Date | Ticket | |--------|-------|----------|--------| | Add connection leak test to CI | @john | 2025-02-22 | #789 | | Add PgBouncer connection pooling | @jane | 2025-03-01 | #790 | | Update migration review checklist | @team | 2025-02-18 | #791 |
8. Diagram-as-Code
8.1 Mermaid β Sequence diagram
mermaid
sequenceDiagram
participant C as Client
participant A as API Gateway
participant S as Auth Service
participant D as Database
C->>A: POST /login {email, password} A->>S: Validate credentials S->>D: SELECT user WHERE email = ? D-->>S: User record S->>S: Verify password hash alt Valid credentials S-->>A: JWT token A-->>C: 200 {token, refreshToken} else Invalid credentials S-->>A: Authentication failed A-->>C: 401 Unauthorized end
8.2 Mermaid β Architecture (C4-style)
mermaid
graph TB
subgraph "Client Layer"
WEB[Web App
React]
MOB[Mobile App
React Native]
end
subgraph "API Layer"
GW[API Gateway
Nginx]
AUTH[Auth Service
Node.js]
API[Core API
FastAPI]
end
subgraph "Data Layer" PG[(PostgreSQL)] RD[(Redis Cache)] S3[S3 Storage] end
WEB --> GW MOB --> GW GW --> AUTH GW --> API AUTH --> PG AUTH --> RD API --> PG API --> RD API --> S3
8.3 Mermaid β Entity Relationship
mermaid
erDiagram
USER ||--o{ ORDER : places
USER {
uuid id PK
string email UK
string name
timestamp created_at
}
ORDER ||--|{ ORDER_ITEM : contains
ORDER {
uuid id PK
uuid user_id FK
decimal total
string status
}
ORDER_ITEM }o--|| PRODUCT : references
ORDER_ITEM {
uuid id PK
uuid order_id FK
uuid product_id FK
int quantity
decimal price
}
PRODUCT {
uuid id PK
string name
string category
decimal price
}
8.4 Mermaid β Flowchart
mermaid
flowchart TD
A[User submits form] --> B{Input valid?}
B -->|Yes| C[Create record in DB]
B -->|No| D[Show validation errors]
D --> A
C --> E{Email unique?}
E -->|Yes| F[Send welcome email]
E -->|No| G[Show duplicate error]
G --> A
F --> H[Redirect to dashboard]
8.5 Mermaid β State diagram
mermaid
stateDiagram-v2
[*] --> Draft
Draft --> InReview: Submit
InReview --> Draft: Request Changes
InReview --> Approved: Approve
Approved --> Published: Publish
Published --> Archived: Archive
Draft --> Archived: Discard
Archived --> [*]
8.6 D2 diagrams
# D2 architecture diagram
direction: rightclient: Client {
web: Web App
mobile: Mobile App
}
api: API Layer {
gateway: API Gateway
auth: Auth Service
core: Core API
}
data: Data Layer {
pg: PostgreSQL {shape: cylinder}
redis: Redis {shape: cylinder}
}
client.web -> api.gateway
client.mobile -> api.gateway
api.gateway -> api.auth
api.gateway -> api.core
api.auth -> data.pg
api.core -> data.pg
api.core -> data.redis
8.7 ASCII art diagrams (for inline code comments)
Request Flow:
Client βββ Load Balancer βββ App Server βββ Database
β β β β
β (round-robin) (connection (pg_pool)
β pooling) β
βββββββββ Response βββββ Response βββββ Query Result
9. Writing Style Guide
9.1 Core principles
| Principle | Bad | Good |
|-----------|-----|------|
| Be direct | "It should be noted that the function might return null" | "The function returns null if the user is not found" |
| Use active voice | "The configuration file is read by the server" | "The server reads the configuration file" |
| Use present tense | "This command will create a directory" | "This command creates a directory" |
| Address the reader | "One should configure the timeout" | "Configure the timeout in config.yml" |
| Be specific | "Set the timeout to a reasonable value" | "Set the timeout to 30000 (30 seconds)" |
| Avoid jargon | "Hydrate the SSR payload" | "Load the server-rendered data on the client" |
9.2 Formatting conventions
| Element | Format | Example |
|---------|--------|---------|
| File paths | Inline code | src/config.ts |
| CLI commands | Code block | npm install |
| Config values | Inline code | Set timeout to 5000 |
| First mention of a term | Bold | Service worker is a background script... |
| UI elements | Bold | Click Settings > Advanced |
| Keyboard shortcuts | Kbd style | Press Ctrl+S to save |
| Variable names | Inline code | The userId parameter |
| Status/state | Inline code | The request returns 200 OK |
9.3 Sentence structure
npm install" not "You need to run npm install"tsconfig.json" not "Add a tsconfig.json if using TypeScript"9.4 Inclusive language
| Avoid | Use instead | |-------|-------------| | master/slave | primary/replica, leader/follower | | whitelist/blacklist | allowlist/blocklist | | sanity check | confidence check, quick check | | dummy value | placeholder value, sample value | | he/she | they | | simple/easy | straightforward |
10. Documentation Site Generators
10.1 Comparison
| Feature | Docusaurus | Starlight | MkDocs Material | VitePress | |---------|-----------|-----------|-----------------|-----------| | Framework | React | Astro | Python/Jinja | Vue | | Setup effort | Low | Low | Low | Low | | Versioning | Built-in | Plugin | Plugin (mike) | Manual | | Search | Algolia/local | Pagefind | Built-in | MiniSearch | | i18n | Built-in | Built-in | Plugin | Manual | | MDX support | Yes | Yes | No (but extensions) | Yes | | Build speed | Medium | Fast | Fast | Fast | | Best for | Large docs sites | Modern docs | Python projects | Vue ecosystem |
10.2 Docusaurus quick start
npx create-docusaurus@latest my-docs classic --typescript
cd my-docs
npm start
my-docs/
βββ docs/
β βββ intro.md
β βββ getting-started/
β β βββ installation.md
β β βββ configuration.md
β βββ api/
β βββ reference.md
βββ blog/
βββ src/
β βββ pages/
βββ docusaurus.config.ts
βββ sidebars.ts
10.3 Starlight (Astro) quick start
npm create astro@latest -- --template starlight my-docs
cd my-docs
npm run dev
10.4 VitePress quick start
npx vitepress init
npm run docs:dev
11. Docs-as-Code Workflow
11.1 CI checks for documentation
# .github/workflows/docs.yml
name: Docs CIon:
pull_request:
paths: ["docs/", "/*.md"]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Markdown lint
uses: DavidAnson/markdownlint-cli2-action@v18
with:
globs: "**/*.md"
- name: Spell check
uses: streetsidesoftware/cspell-action@v6
with:
files: "docs/**/*.md"
- name: Check links
uses: lycheeverse/lychee-action@v2
with:
args: --no-progress "docs/**/*.md"
fail: true
- name: Build docs
run: npm run docs:build
11.2 markdownlint configuration
// .markdownlint.json
{
"MD013": false,
"MD033": { "allowed_elements": ["br", "details", "summary", "img"] },
"MD041": false,
"MD024": { "siblings_only": true }
}
11.3 cspell configuration
// cspell.json
{
"version": "0.2",
"language": "en",
"words": [
"docusaurus", "starlight", "vitepress", "mkdocs",
"preconfigured", "monorepo", "runbook", "frontmatter"
],
"ignorePaths": ["node_modules", "dist", "package-lock.json"]
}
12. Code Examples in Documentation
12.1 Principles
1. Runnable β examples should work if copy-pasted
2. Realistic β use domain-relevant variable names, not foo/bar
3. Progressive β start simple, add complexity gradually
4. Complete β show imports, setup, and cleanup
5. Tested β run examples in CI to prevent rot
12.2 Example structure
// BAD: incomplete, unrealistic
const x = await fn({ a: 1 });// GOOD: complete, realistic, commented
import { createClient } from "@example/sdk";
// Initialize with your API key (get one at https://example.com/keys)
const client = createClient({
apiKey: process.env.EXAMPLE_API_KEY!,
});
// Fetch a list of users with pagination
const users = await client.users.list({
limit: 20,
cursor: undefined, // First page
});
console.log(Found ${users.total} users);
for (const user of users.data) {
console.log(- ${user.name} (${user.email}));
}
12.3 Testing code examples
# Extract code blocks from markdown and run them
npx ts-node scripts/test-examples.tsOr use doctest-style testing
Python: pytest --doctest-modules
Rust: cargo test --doc
13. Common Pitfalls
| Pitfall | Impact | Fix |
|---------|--------|-----|
| Writing docs after the project is "done" | Docs never get written; knowledge is lost | Write docs alongside code, in the same PR |
| No clear audience | Docs try to serve everyone, serve no one | State prerequisites and audience at the top |
| Tutorial disguised as reference | Beginners get lost in details; experts skip it | Separate tutorials from reference docs (Diataxis) |
| Undocumented prerequisites | Users fail at step 1 | List all required tools, versions, and setup steps |
| Stale code examples | Users copy broken code, lose trust | Test code examples in CI; pin dependency versions |
| Screenshots without alt text | Accessibility failure; breaks if image URL changes | Always add descriptive alt text; prefer text over screenshots |
| No changelog | Users don't know what changed between versions | Maintain a CHANGELOG.md; automate from conventional commits |
| Docs not in version control | No review process, no history, no blame | Store docs in the same repo as code |
| Wall of text | Readers skim and miss key information | Use headings, lists, tables, code blocks; chunk information |
| Assuming context | New readers don't have your mental model | Link to prerequisite docs; explain acronyms on first use |
| No search | Users can't find what they need | Use Algolia, Pagefind, or built-in search in doc generators |
| Broken internal links | Readers hit dead ends | Run link checker in CI (lychee, markdown-link-check) |
| Mixing instructions with explanation | Readers lose track of what to do | Put context in a separate section; keep steps action-only |
| No contribution guide | Only the original author updates docs | Add CONTRIBUTING.md with docs guidelines |
| Translating literally | Translated docs read unnaturally | Use i18n tools (Crowdin); have native speakers review |
| Over-documenting internal code | Maintenance burden with no external benefit | Document public APIs; use @internal for private code |
π‘ Examples
Basic usage
// Minimal example showing the most common use case
Advanced usage
// Example showing configuration options, edge cases, or composition
βοΈ Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| debug | boolean | false | Enable debug logging |
| timeout | number | 5000 | Request timeout in ms |