Skip to content

Layout & text

View .mdllms.txt
import { Layer } from "framefields";
Layer.flex({
width: 1920, height: 1080,
dir: "column", // "row" | "column"
justify: "center", // start | center | end | space-between | space-around
align: "center", // start | center | end | stretch
gap: 24, padding: 80,
wrap: false,
background: "#0d0f12",
children: [
Layer.box({ padding: 16, background: "#1e232d", borderRadius: 20, children: [/* … */] }),
],
});
Prop Notes
width, height px, "auto" (intrinsic), "fit" (shrink-wrap) or "fill" (parent extent)
position: "absolute" + x, y take the layer out of flow; x/y alone also make it absolute
background a color or a CSS gradient string
borderRadius, borderColor, borderWidth no CSS border shorthand
overflow: "hidden" clip children to the box
opacity, scale, rotation static or animated
startFrame, durationFrames when the layer is on screen
id stable name; used by lint/QA messages and chart children
Layer.shape("circle", { width: 200, height: 200, fill: "#ff5a1f" });
Layer.shape("star", { width: 240, height: 240, fill: "radial-gradient(#fff, #ffb800)" });
Layer.shape("path", { d: "M0 0 L200 0 A100 100 0 0 1 0 0Z", strokeColor: "#fff", strokeWidth: 4, trimEnd: 0 })
.animate(LayerAnimation.create().fromTo("trimEnd", 0, 1, { start: 0, end: 30 })); // draw-on

Types: rect, circle, ellipse, polygon, star, arrow, path. Animate trimStart / trimEnd / trimOffset for stroke draw-ons.

There are no system fonts in headless WebGPU. Register every family before building any text layer.

import { FontManager } from "framefields";
await FontManager.register({ family: "Inter", source: "assets/fonts/Inter.ttf" });
await FontManager.register({ family: "Inter", source: "assets/fonts/Inter-Bold.ttf", weight: 700 });
await FontManager.registerEmojiFont("assets/fonts/NotoColorEmoji.ttf"); // emoji need a CBDT/CBLC color font
Layer.text("PRECISION MOTION", {
fontFamily: "Inter",
fontSize: 96,
fontWeight: 700,
fill: "#ffffff",
letterSpacing: 2,
lineHeight: 104,
width: 1400, // wrap width; set it or text may clip
align: "center", // "start" | "center" | "end" (follows text direction)
verticalAlign: "middle", // "top" | "middle" | "bottom" inside the box height
});

Text is drawn by the Slug algorithm: glyph curves are evaluated per pixel in the shader, so it stays sharp at any size and under 3D camera moves. Arabic, Hebrew and Persian shape right-to-left automatically (direction: "auto"; force with "rtl" / "ltr").

import { Layer, bold, color, mark, rich } from "framefields";
Layer.text(
rich`Scale ${bold("10x", { fill: "#6366f1" })} faster with ${mark("120 FPS", { bg: "#10b98133" })}`,
{ fontFamily: "Inter", fontSize: 56, fill: "#f8fafc" },
);

Span helpers: span, bold, italic, color, mark, size, sub, sup. A span takes fontFamily, fontSize, fontWeight, fill, letterSpacing, baselineShift, opacity.

const m = FontManager.measure("Hello, world", { fontFamily: "Inter", fontSize: 64 });
m.width; m.height; m.lines; m.caretX(5); m.glyphs[0].x; // px, relative to the text box

Use it to place carets, underline a word, or size a pill to its label instead of guessing.

Every helper returns a hex string.

import { Color } from "framefields";
Color.oklch(0.72, 0.16, 250); // equal L = equal perceived brightness
Color.mix("#ff5a1f", "#1f6bff", 0.5); // perceptual (OKLab) mix
Color.ramp(["#3b82f6", "#8b5cf6", "#ec4899"])(0.3);
Color.steps(["#0e0d0c", "#ff5a1f"], 5); // 5 swatches
Color.lighten(c, 0.1); Color.darken(c, 0.1); Color.alpha("#000", 0.4);
Color.contrast(fg, bg); // WCAG ratio: ≥ 4.5 body, ≥ 3 large
Color.readableOn(bg); // black or white, whichever reads

CSS strings, usable in any background and any shape fill. Linear, radial and conic, up to 8 stops, hard stops allowed.

import { Gradient } from "framefields";
Layer.box({ background: "linear-gradient(135deg, #ff5a1f, #ffb800 60%, #7c3aed)" });
Layer.shape("circle", { fill: Gradient.radial(["#fff", "transparent"]) });
Layer.box({ background: Gradient.conic(["red", "yellow", "lime", "aqua", "blue", "magenta", "red"]) });

A gradient that doesn’t parse draws nothing; comp.lint() reports it as invalid_gradient.

node.withMatte(matte, mode) shows node only where the matte is opaque ("alpha", default), transparent ("alpha-inverted"), bright ("luma") or dark ("luma-inverted"). The matte is laid out like a sibling and is never drawn itself.

// Text revealed by a sliding bar
Layer.text("LAUNCH", { position: "absolute", x: 560, y: 480, width: 800, height: 120, fontSize: 120 })
.withMatte(
Layer.box({ position: "absolute", x: 560, y: 480, width: 800, height: 120, background: "#fff" })
.animate(LayerAnimation.keys("x", [[0, -240], [24, 560, "expo.out"]])),
);
// Video inside type
Layer.video("assets/footage/surf.mp4", { position: "absolute", x: 0, y: 0, width: 1920, height: 1080, fit: "cover" })
.withMatte(Layer.text("OCEAN", { position: "absolute", x: 0, y: 340, width: 1920, fontSize: 380, align: "center", fill: "#fff" }));