tl-docs-viewer-create
Documentation Viewer UI
Create a browseable documentation viewer for admin interfaces with tree navigation, markdown rendering, and Mermaid diagram support.
When to Use
- "create docs viewer"
- "add documentation browser"
- "admin docs UI"
- "browse docs folder"
- "docs viewer component"
- Adding a docs/ browser to an existing admin area
- Need to view markdown documentation in-app
Outcomes
- Artifact: React page component with three-column layout (tree + content + TOC)
- Artifact: Server API endpoints for tree and content
- Artifact: Supporting components (DocTree, MermaidMarkdown, OnThisPageNav)
- Decision: Route placement and library choices
Configuration Discovery
Before implementation, gather project context through structured questions. See references/configuration.md for full schemas.
Question Flow
flowchart TD
Start[Trigger skill] --> Scan[Scan for admin patterns]
Scan --> Q1[Ask: Admin Area]
Q1 --> Q2[Ask: Frontend Stack]
Q2 --> Q3[Ask: Route Placement]
Q3 --> Q4[Ask: Layout Pattern]
Q4 --> Q5[Ask: Library Preferences]
Q5 --> Implement[Implement viewer]
Questions Summary
- Admin Area Detection — Existing admin area? (yes/no/scan)
- Frontend Stack — React Router / Wouter / Next.js / TanStack Router / Remix
- Route Placement — Detected path / /admin/docs / /docs / custom
- Layout Pattern — Three-column / Two-column / Single column
- Library Preferences — Markdown renderer + data fetching choices
Architecture
Three-Column Layout (Default)
┌─────────────────────────────────────────────────────────────â”
│ Admin Docs Layout │
├──────────┬───────────────────────────────────┬──────────────┤
│ │ │ │
│ DocTree │ DocContent │ OnThisPage │
│ (250px) │ (flex-1) │ (200px) │
│ │ │ │
│ ├─ docs │ # Document Title │ - Section 1 │
│ │ ├─ a │ │ - Section 2 │
│ │ └─ b │ Content rendered from markdown │ - Sub 2.1 │
│ └─ ... │ │ - Section 3 │
│ │ │ │
└──────────┴───────────────────────────────────┴──────────────┘
Data Flow
flowchart TD
subgraph client [Client]
Page[DocsViewerPage] --> Tree[DocTree]
Page --> Content[DocContent]
Page --> TOC[OnThisPageNav]
Tree -->|select| Router[Router]
Router -->|path change| Content
end
subgraph server [Server]
TreeAPI["GET /admin/docs/tree"]
ContentAPI["GET /admin/docs/content/*"]
end
Tree -->|fetch| TreeAPI
Content -->|fetch| ContentAPI
Phase 1: Server API
Create two endpoints. See references/server-api.md for full patterns.
GET /admin/docs/tree
Returns folder structure as JSON tree.
interface DocNode {
name: string;
path: string;
type: 'file' | 'folder';
children?: DocNode[];
}
GET /admin/docs/content/:path*
Returns markdown content and metadata.
interface DocContent {
content: string;
title: string;
lastUpdated?: string;
path: string;
}
Phase 2: React Components
Create the component hierarchy. See references/react-components.md for full architecture.
Components
| Component | Purpose |
|---|---|
AdminDocsLayout |
Three-column layout wrapper |
DocTree |
Recursive tree navigation |
DocTreeItem |
Single tree node with expand/collapse |
MermaidMarkdown |
Markdown renderer with Mermaid support |
OnThisPageNav |
TOC generated from headings |
Phase 3: Integration
Route Setup
Based on detected frontend stack:
| Stack | Route Pattern |
|---|---|
| React Router | <Route path="/admin/docs/*" element={<DocsViewer />} /> |
| Wouter | <Route path="/admin/docs/:path*" component={DocsViewer} /> |
| Next.js | app/admin/docs/[[...path]]/page.tsx |
| TanStack Router | createRoute({ path: '/admin/docs/$path', component: DocsViewer }) |
Data Fetching
Based on library preference:
| Library | Pattern |
|---|---|
| TanStack Query | useQuery({ queryKey: ['docs', 'tree'], queryFn: fetchTree }) |
| SWR | useSWR('/admin/docs/tree', fetcher) |
| Native fetch | useEffect + useState pattern |
Dependencies
Configurable via AskQuestion:
| Category | Default | Alternatives |
|---|---|---|
| Markdown | @uiw/react-markdown-preview | react-markdown, marked |
| Data fetching | @tanstack/react-query | swr, native fetch |
| Diagrams | mermaid | Optional |
Verification
After implementation, verify:
- Tree loads and displays folder structure
- Clicking file loads markdown content
- Mermaid diagrams render (if enabled)
- TOC generates from headings
- Route navigation works
- Dark mode supported (if applicable)
References
| File | Purpose |
|---|---|
references/configuration.md |
AskQuestion flows and branching |
references/server-api.md |
API endpoint patterns |
references/react-components.md |
Component architecture |
references/templates/ |
Code templates |
Markdown Rendering
Streamdown (Recommended)
Streaming-optimized React Markdown renderer with built-in Shiki and Mermaid support.
import { Streamdown } from 'streamdown';
import { code } from '@streamdown/code';
import { mermaid } from '@streamdown/mermaid';
<Streamdown
mode="static"
plugins={{ code, mermaid }}
shikiTheme={['github-light', 'github-dark']}
>
{content}
</Streamdown>
Tailwind v4 Setup — Add to globals.css:
@source "../node_modules/streamdown/dist/*.js";
@source "../node_modules/@streamdown/code/dist/*.js";
@source "../node_modules/@streamdown/mermaid/dist/*.js";
Key Props:
| Prop | Type | Purpose |
|---|---|---|
mode |
"streaming" | "static" |
Use static for docs |
plugins |
{ code?, mermaid?, math? } |
Feature plugins |
shikiTheme |
[light, dark] |
Code block themes |
controls |
boolean |
Copy buttons |
Alternative: react-markdown
If not using Streamdown:
import ReactMarkdown from 'react-markdown';
import rehypeHighlight from 'rehype-highlight';
import remarkGfm from 'remark-gfm';
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeHighlight]}
>
{content}
</ReactMarkdown>
Search Integration
Pagefind (Static Search)
Best for pre-built docs. Index at build time, search client-side.
import { search } from '@pagefind/default-ui';
const results = await search(query);
Flexsearch (Client-Side)
Best for dynamic docs loaded at runtime.
import FlexSearch from 'flexsearch';
const index = new FlexSearch.Index();
docs.forEach((doc, id) => index.add(id, doc.content));
const results = index.search(query);
Search Modal Pattern
function SearchModal({ isOpen, onClose }) {
const [query, setQuery] = useState('');
const results = useSearch(query);
return (
<dialog open={isOpen} onClose={onClose}>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Search docs..."
autoFocus
/>
<ul role="listbox">
{results.map(r => (
<li key={r.path} role="option">
<a href={r.path}>{r.title}</a>
</li>
))}
</ul>
</dialog>
);
}
Keyboard Navigation
Required Shortcuts
| Key | Action |
|---|---|
/ or Cmd+K |
Open search |
Esc |
Close search/modal |
↑ ↓ |
Navigate results |
Enter |
Select result |
j k |
Navigate tree (optional) |
Implementation
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === '/' && !isInputFocused()) {
e.preventDefault();
openSearch();
}
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
openSearch();
}
};
document.addEventListener('keydown', handler);
return () => document.removeEventListener('keydown', handler);
}, []);
Accessibility
ARIA Requirements
<nav aria-label="Documentation navigation">
<ul role="tree" aria-label="Docs tree">
<li role="treeitem" aria-expanded={isOpen} aria-selected={isSelected}>
<button onClick={toggle}>{name}</button>
</li>
</ul>
</nav>
<main role="main" aria-label="Documentation content">
<article>{content}</article>
</main>
<nav aria-label="On this page">
<ul>{headings.map(h => <li key={h.id}><a href={`#${h.id}`}>{h.text}</a></li>)}</ul>
</nav>
Focus Management
function DocTree({ items }) {
const [focusedIndex, setFocusedIndex] = useState(0);
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'ArrowDown') setFocusedIndex(i => Math.min(i + 1, items.length - 1));
if (e.key === 'ArrowUp') setFocusedIndex(i => Math.max(i - 1, 0));
if (e.key === 'Enter') selectItem(items[focusedIndex]);
};
return (
<ul role="tree" onKeyDown={handleKeyDown}>
{items.map((item, i) => (
<li
key={item.path}
role="treeitem"
tabIndex={i === focusedIndex ? 0 : -1}
ref={i === focusedIndex ? focusedRef : null}
>
{item.name}
</li>
))}
</ul>
);
}
MDX Support
For interactive documentation with embedded components:
import { compile, run } from '@mdx-js/mdx';
import * as runtime from 'react/jsx-runtime';
async function renderMDX(source: string, components: Record<string, Component>) {
const compiled = await compile(source, { outputFormat: 'function-body' });
const { default: Content } = await run(compiled, runtime);
return <Content components={components} />;
}
Custom Components:
const components = {
CodePlayground: ({ code }) => <LiveEditor code={code} />,
Callout: ({ type, children }) => <aside className={`callout-${type}`}>{children}</aside>,
Steps: ({ children }) => <ol className="steps">{children}</ol>,
};
Documentation Writing Guidelines
From remotion-dev patterns:
- One API per page — Each function/component gets its own page
- Don't assume it's easy — Avoid "simply" and "just"
- Address as "you" — Not "we"
- Keep it brief — Extra words cause information loss
- Use headings for fields — Not bullet points for API options
- Add titles to code fences — Always include file context
Related Skills
- tl-docs-create — Create documentation from scratch
- tl-docs-audit — Audit docs coverage, find gaps, generate sync reports
References
Quilted Skills
- vercel/streamdown — Streaming markdown renderer
- remotion-dev/remotion/writing-docs — Documentation patterns
- vercel/components.build/building-components — Component architecture
First-Party Documentation
- Streamdown — Streaming markdown renderer
- Shiki — Syntax highlighting
- Mermaid — Diagrams
- Pagefind — Static site search
- Flexsearch — Full-text search
- MDX — Markdown + JSX
Accessibility
- WAI-ARIA Tree View — Tree navigation pattern
- WAI-ARIA Combobox — Search modal pattern
- React Aria — Accessible component primitives
More from toddlevy/tl-agent-skills
tl-openmeter-api
Works with the OpenMeter REST API for usage metering, billing, and entitlements. Covers CloudEvents ingestion, meters, features, plans, customers, subscriptions, entitlements, notifications, billing profiles, invoices, apps, addons, grants, and the Stripe marketplace. Use when integrating OpenMeter, debugging metering, building catalog sync scripts, or when the user mentions OpenMeter API.
13tl-first-principles
Foundational software design principles traced to their intellectual origins. Covers information hiding, separation of concerns, abstraction, SSOT/DRY, conceptual integrity, and composition. Use when making architectural decisions, evaluating trade-offs, or understanding *why* best practices exist.
13tl-knip
Find and remove unused files, dependencies, and exports in TypeScript/JavaScript projects using Knip. Covers configuration-first workflow, plugin system, barrel file handling, CI integration, monorepo support, and agent-specific cleanup guidance.
12tl-docs-create
Create documentation from scratch for codebases. Covers SSOT-driven generation, writing standards, and templates for README/AGENTS.md/CHANGELOG. Use when creating new docs or documenting an undocumented codebase.
12tl-devlog
Maintain a structured development changelog (DEVLOG.md) capturing architectural decisions, milestones, incidents, and insights. Use when the user says "log this", "devlog", "archive this", or at natural pause points after significant decisions. Trigger on changelog, decision log, work log, or progress tracking.
12tl-docs-audit
Audit existing documentation for gaps, staleness, and sync issues. Generates sync reports with actionable findings. Use when reviewing doc coverage, finding outdated docs, or syncing docs with code.
12