Using Html
Render annotated boxes as HTML markup. Use this renderer when your output belongs in a browser, a generated page, or an HTML fragment. For terminal colors, use Using Ansi instead.
Each example runs independently. The terminal previews show the generated markup. The browser previews render that same markup in an isolated frame with browser default styles, except where the example supplies CSS.
Render headings and paragraphs
Attach an element annotation to each box, then provide Renderer.HtmlRendererLive
to Box.render. Effect.runSync executes this renderer and returns an HTML string.
<h1>
Hello World
</h1>
<p>
Welcome to the docs.
</p>import * as Box from "effect-boxes/Box";
import * as Html from "effect-boxes/Html";
import * as Renderer from "effect-boxes/Renderer";
import { Effect, pipe } from "effect";
const heading = pipe(Box.text("Hello World"), Box.annotate(Html.h1()));
const paragraph = pipe(
Box.text("Welcome to the docs."),
Box.annotate(Html.p())
);
const page = Box.vcat([heading, paragraph], Box.left);
const html = Effect.runSync(
Box.render(page).pipe(Effect.provide(Renderer.HtmlRendererLive))
);
const output = html;
console.log(output);Add attributes
Every element constructor accepts an optional record of string attribute values.
Use HTML names such as class, rather than React names such as className.
The renderer escapes attribute values, including the ampersand in this link's title.
<a href="https://example.com" title="Examples & guides">
Example website
</a>
<div class="highlight" id="message">
Highlighted
</div>import * as Box from "effect-boxes/Box";
import * as Html from "effect-boxes/Html";
import * as Renderer from "effect-boxes/Renderer";
import { Effect } from "effect";
const link = Box.text("Example website").pipe(
Box.annotate(
Html.a({ href: "https://example.com", title: "Examples & guides" })
)
);
const highlighted = Box.text("Highlighted").pipe(
Box.annotate(Html.div({ class: "highlight", id: "message" }))
);
const page = Box.vcat([link, highlighted], Box.left);
const output = Effect.runSync(
Box.render(page).pipe(Effect.provide(Renderer.HtmlRendererLive))
);
console.log(output);A class attribute refers to your application's stylesheet. The renderer does
not supply CSS for highlight, and the generated string does not mount itself
into the page.
Compose elements and apply CSS
Use a row or column to group annotated children, then annotate the group with its
parent element. Both Box.hcat and Box.vcat preserve document order in HTML.
Use CSS to arrange browser elements. Here, display: flex and gap place the
navigation links side by side.
<article style="font-family: sans-serif; padding: 1rem">
<nav style="display: flex; gap: 1rem">
<a href="/guides/using-box">
Using Box
</a>
<a href="/guides/using-html">
Using Html
</a>
</nav>
<section>
<h2>
Welcome
</h2>
<p>
This page uses HTML elements.
</p>
</section>
</article>import * as Box from "effect-boxes/Box";
import * as Html from "effect-boxes/Html";
import * as Renderer from "effect-boxes/Renderer";
import { Effect } from "effect";
const nav = Box.hcat(
[
Box.text("Using Box").pipe(
Box.annotate(Html.a({ href: "/guides/using-box" }))
),
Box.text("Using Html").pipe(
Box.annotate(Html.a({ href: "/guides/using-html" }))
),
],
Box.top
).pipe(Box.annotate(Html.nav({ style: "display: flex; gap: 1rem" })));
const content = Box.vcat(
[
Box.text("Welcome").pipe(Box.annotate(Html.h2())),
Box.text("This page uses HTML elements.").pipe(Box.annotate(Html.p())),
],
Box.left
).pipe(Box.annotate(Html.section()));
const page = Box.vcat([nav, content], Box.left).pipe(
Box.annotate(
Html.article({ style: "font-family: sans-serif; padding: 1rem" })
)
);
const output = Effect.runSync(
Box.render(page).pipe(Effect.provide(Renderer.HtmlRendererLive))
);
console.log(output);Box widths, alignment, padding, and borders describe terminal cells. The HTML renderer does not translate those dimensions or borders into CSS. Define the browser layout with attributes and your stylesheet.
Box.annotate replaces the annotation on the current box. To nest two elements,
wrap the annotated child in a row or column before annotating the parent, as the
article above does. See Using Annotation for nesting.
Choose output formatting
Both HTML renderer layers put tags and content on separate lines.
HtmlRendererLive omits indentation and trims trailing whitespace.
HtmlPrettyRendererLive adds indentation and preserves trailing whitespace.
Neither layer minifies HTML.
Default:
<div>
<strong>
Ready
</strong>
</div>
Pretty:
<div>
<strong>
Ready
</strong>
</div>import * as Box from "effect-boxes/Box";
import * as Html from "effect-boxes/Html";
import * as Renderer from "effect-boxes/Renderer";
import { Effect } from "effect";
const page = Box.vcat(
[Box.text("Ready").pipe(Box.annotate(Html.strong()))],
Box.left
).pipe(Box.annotate(Html.div()));
const compact = Effect.runSync(
Box.render(page).pipe(Effect.provide(Renderer.HtmlRendererLive))
);
const pretty = Effect.runSync(
Box.render(page).pipe(Effect.provide(Renderer.HtmlPrettyRendererLive))
);
const output = ["Default:", compact, "", "Pretty:", pretty].join("\n");
console.log(output);Browser HTML normally collapses whitespace. To preserve visible spacing, use a
pre element or a CSS white-space rule. Pretty output changes the source string;
it does not create a terminal-style layout in the browser.
Escape text and attribute values
Pass unescaped text and attribute values to the constructors. The renderer escapes them automatically. Pre-escaped entities would be escaped a second time. In this preview, the script-like text appears as text.
<p title="A "quoted" value & more">
<script>alert('xss')</script> & friends
</p>import * as Box from "effect-boxes/Box";
import * as Html from "effect-boxes/Html";
import * as Renderer from "effect-boxes/Renderer";
import { Effect } from "effect";
const userInput = "<script>alert('xss')</script> & friends";
const message = Box.text(userInput).pipe(
Box.annotate(Html.p({ title: 'A "quoted" value & more' }))
);
const output = Effect.runSync(
Box.render(message).pipe(Effect.provide(Renderer.HtmlRendererLive))
);
console.log(output);Escaping protects HTML syntax. Use trusted attribute names and validate URL
schemes when values come from users. Escaping a javascript: URL does not make
that URL safe. The preview frames disable scripts through their sandbox.
For text inserted into HTML outside this renderer, use Html.escapeHtml:
<hello & world>import * as Html from "effect-boxes/Html";
const userInput = "<hello & world>";
const output = Html.escapeHtml(userInput);
console.log(output);Create empty elements
Use Box.nullBox for void elements such as br and hr. These elements have no
content or closing tag. Annotating a nonempty text box with a void element keeps
the text and does not emit that element.
<hr />import * as Box from "effect-boxes/Box";
import * as Html from "effect-boxes/Html";
import * as Renderer from "effect-boxes/Renderer";
import { Effect } from "effect";
const separator = Box.nullBox.pipe(Box.annotate(Html.hr()));
const output = Effect.runSync(
Box.render(separator).pipe(Effect.provide(Renderer.HtmlRendererLive))
);
console.log(output);Available elements
| Constructors | HTML elements | Use |
|---|---|---|
div, span | <div>, <span> | Block and inline containers |
p, h1 through h6 | Paragraphs and headings | Text structure |
section, article | <section>, <article> | Content groups |
header, footer, main, nav, aside | Semantic containers | Page structure |
ul, ol, li | Lists and list items | List structure |
a | <a> | Links |
strong, em | <strong>, <em> | Emphasis |
code, pre | <code>, <pre> | Code and preformatted text |
br, hr | <br>, <hr> | Empty line break and separator elements |
The renderer writes the elements you request. It does not validate HTML nesting or add browser event handlers.
Next steps
See the Html API reference for constructor signatures and Rendering to compare HTML, plain text, and ANSI output.