Renderizado do repositório de origem, preservando títulos, exemplos, código, tabelas, links e imagens.
<!-- Copyright (c) 2026 Todd Levy. Licensed under MIT. SPDX-License-Identifier: MIT -->
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| ContentAPIPhase 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

