# HTML Rendering
Two classes turn a Viper document into HTML. `DocumentNode` walks a document as a
typed tree, carrying the metadata a renderer needs — component name, value,
editability, and the concrete type behind each leaf. `Html` renders, either the whole
tree in one call or one piece at a time.
**When to use**: reach for {js:class}`Html` when the default rendering will do, and
for {js:class}`DocumentNode` directly when the markup is yours — a form, a custom
layout, a template engine's data. Nothing here is browser-specific: the same tree
feeds a server-rendered page, and the Qt widgets do the same thing with it.
## Quick Start
An Express route rendering one document as a collapsible tree:
```js
const express = require('express');
const { CommitDatabase, CommitStateBuilder, DocumentNode, Html, ValueKey } =
require('@digitalsubstrate/dsviper');
const app = express();
app.get('/document/:instanceId', (req, res) => {
const db = CommitDatabase.open('model.cdb');
try {
const key = ValueKey.create(concept, ValueUUId.create(req.params.instanceId));
const getting = CommitStateBuilder.state(db, db.lastCommitId()).attachmentGetting();
// Build the document tree for every attachment on that key.
const nodes = DocumentNode.createDocuments(key, getting);
res.send(Html.document('Document', Html.style(),
Html.body(Html.documentsDetails(nodes, true))));
} finally { db.close(); }
});
```
`Html.style()` returns the stylesheet the other helpers assume, `Html.body()` wraps
content, and `Html.document()` assembles a standalone page — so a working page needs
no CSS of its own.
## Walking the tree yourself
`DocumentNode` answers what a renderer needs to decide, so a custom renderer is a
recursion over predicates:
```js
function renderNode(node, level = 0) {
if (node.isExpandable()) {
const children = node.children().map((c) => renderNode(c, level + 1)).join('');
return `${node.stringComponent()}
${children}