Using Annotation
The Annotation module provides a type-safe system for attaching metadata to
boxes. Annotations are how effect-boxes adds styling, interactivity, and
custom data to text layouts.
How Annotations Work
A Box<A> is parameterized by its annotation type A. When you create a
plain box with Box.text("hello"), it has type Box<never> — no annotations.
When you attach an annotation, the box's type reflects what kind of metadata
it carries.
import * as Box from "effect-boxes/Box"
import * as Ansi from "effect-boxes/Ansi"
import { pipe } from "effect"
// Box<never> — no annotation
const plain = Box.text("Hello")
// Box<AnsiStyle> — carries ANSI styling
const styled = pipe(plain, Box.annotate(Ansi.red))HelloRenderers interpret annotations at render time. The AnsiRendererLive knows
how to turn AnsiStyle annotations into escape codes. The PlainRendererLive
strips all annotations.
Creating Annotations
Use Annotation.createAnnotation to wrap any data as an annotation:
import * as Annotation from "effect-boxes/Annotation"
// Simple string metadata
const tag = Annotation.createAnnotation("important")
// Structured data
const metadata = Annotation.createAnnotation({
id: "submit-btn",
action: "submit-form",
enabled: true,
})Annotations implement Effect's Equal and Hash interfaces, so two
annotations with the same data are structurally equal.
Attaching Annotations to Boxes
Use Box.annotate (dual function — works data-first or data-last):
// Data-last with pipe
const success = pipe(
Box.text("Success!"),
Box.annotate(Ansi.combine(Ansi.bold, Ansi.green))
)
// Data-first
const error = Box.annotate(Box.text("Error!"), Ansi.red)
Box.vcat([success, error], Box.left)Success!
Error!Manipulating Annotations
Removing Annotations
// Strip all annotations from a box
const plain = Box.unAnnotate(styledBox)Transforming Annotation Data
// Transform the annotation data on a box
const updated = Box.reAnnotate(annotatedBox, (data) =>
data.toUpperCase()
)Nested Annotations
When boxes with different annotations are composed, each region keeps its own annotation. The innermost annotation takes precedence during rendering:
const multiColor = Box.hcat([
pipe(Box.text("Red "), Box.annotate(Ansi.red)),
pipe(Box.text("Green "), Box.annotate(Ansi.green)),
pipe(Box.text("Blue"), Box.annotate(Ansi.blue)),
], Box.top)Red Green BlueAnnotation Utilities
The Annotation module provides utilities for working with annotation values
directly:
import * as Annotation from "effect-boxes/Annotation"
// Type guards
Annotation.isAnnotation(value)
Annotation.isAnnotationWithData(value, (d): d is MyType => /* ... */)
// Transform annotation data
Annotation.mapAnnotationData(annotation, (d) => transform(d))
// Combine two annotations with a merger function
Annotation.combineAnnotations(a, b, (dataA, dataB) => merged)
// Conditionally keep an annotation
Annotation.filterAnnotation(annotation, (data) => data.enabled)
// Batch create from array
Annotation.createAnnotations(["a", "b", "c"])Built-in Annotation Types
effect-boxes provides three annotation modules:
| Module | Type | Purpose |
|---|---|---|
| Ansi | AnsiStyle | Terminal colors and text effects |
| Cmd | AnsiStyle | Terminal control sequences (cursor, screen) |
| Html | HtmlAnnotationData | HTML elements for web rendering |
Each has a corresponding renderer that interprets its annotations at render time. See the Rendering guide for details.