Skip to content

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.

Generated HTML
<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.

Link and container markup
<a href="https://example.com" title="Examples &amp; 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.

Navigation and article markup
<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 and pretty markup
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.

Escaped markup
<p title="A &quot;quoted&quot; value &amp; more">
&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt; &amp; 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:

Escaped string
&lt;hello &amp; world&gt;
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.

Horizontal rule markup
<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

ConstructorsHTML elementsUse
div, span<div>, <span>Block and inline containers
p, h1 through h6Paragraphs and headingsText structure
section, article<section>, <article>Content groups
header, footer, main, nav, asideSemantic containersPage structure
ul, ol, liLists and list itemsList 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.