Skip to content

Example: the free-form text box

No manifest row · built today as betta-text-box

A fixed-size yellow panel with brand-blue copy centred inside it, wrapping to the panel's width. The docx describes it as "a free form text box the user can type into directly": the author places and sizes the box once, and whatever gets typed wraps inside it — a longer line wraps rather than resizing the panel. It wipes on from the left.

flowchart TB
    subgraph scene["scene-text-box"]
        BOX["text-box<br/>text · absolute · fixed box 417 × 278<br/>wipe from left"]
    end

The document

{
  "id": "scene-text-box",
  "durationInFrames": 75,
  "layers": [

    // ── One layer. Both axes are numbers, so this is the FIXED-BOX text shape: wrap to
    //    width, then shrink to fit height if the copy still overflows. The panel fills the
    //    authored box; padding insets the copy from its edges.
    {
      "id": "text-box",
      "type": "text",
      "zIndex": 0,
      "layout": {
        "mode": "absolute",
        "16:9": { "x": 120, "y": 300, "w": 417, "h": 278, "anchor": "top-left" },
        "9:16": { "x": 120, "y": 600, "w": 417, "h": 278, "anchor": "top-left" },
        "1:1":  { "x": 120, "y": 300, "w": 417, "h": 278, "anchor": "top-left" }
      },
      "typography": {
        "16:9": { "family": "GT Eesti Display", "weight": 700, "size": 60, "color": "#054F8E",
                  "lineHeight": 1.05, "multiline": true,
                  "horizontalAlign": "center", "verticalAlign": "middle" }
        // "9:16": …, "1:1": …
      },
      "props": {
        "content": [ { "value": "Your text here" } ],
        "background": { "fill": "#FFD100", "padding": { "top": 24, "right": 32, "bottom": 24, "left": 32 }, "radius": 8 },
        "overflow": "shrink"                                  // only meaningful in this shape
      },
      "animations": [
        { "animation": "wipe", "params": { "from": "left", "durationInFrames": 12 },
          "at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 0 } }
      ]
    }

    // The same component as the AUTO-HEIGHT shape — "grow with what I type" instead of
    // "shrink into the box I drew" — is one field: "h": "content". Then the panel is exactly
    // as tall as the wrapped copy plus padding, and `overflow` has nothing to act on.
  ]
}
Bound field Control
text-box.props.content[0].value text (multiline)
text-box.typography.*.color, text-box.props.background.fill colour
text-box.layout.*.w, text-box.layout.*.h number

What the engine does with it

Resolve. One wipe expansion. Typography is already absolute.

Solve. Width is a number, so measure runs with a constraint: 417 − 32 − 32 = 353 px of line width. Your text here fits one line; a long paragraph wraps to several. Height is also a number, so if the wrapped block is taller than 278 − 24 − 24 = 230, the wrap-then-shrink solver scales the type down until it fits (Text Foundation → the three shapes). The box is 417 × 278 regardless of the copy.

Animate. The wipe is a clip-reveal, not a transform, so it rides the node's own paint; the box itself doesn't move.

Place. One element: the panel fills the box, the wrapped, possibly shrunk lines sit centred inside the padded area.

What this shows

  • Three shapes, one type. content/content hugs, number/content grows, number/number wraps then shrinks — this page is the third; flip h to "content" for the second. Nothing about the layer type changes.
  • "Types into it directly" means the box is the constraint. The author's drawn box is the contract; the copy adapts to it, never the other way round.

Stress points

  1. Padding is a layout input in the fixed-box shape, and the brief says it never is. Text Foundation → Background padding is paint-only states padding "must never influence the text's own position, contribute to its box, or trigger a resize/rescale". For a point-text pill that's right: the pill grows outward from the ink. For a fixed box it's backwards: the panel is the authored box, and padding must inset the wrap width and the shrink height, or the copy runs to the fill's edges — exactly what the built component does (box-sizing: border-box, padding inside a 100%-sized panel). The rule needs a per-axis statement: on a "content" axis padding is a paint-only outset from the ink; on a number axis it is an inset that reduces the measure constraint. The "safe to enforce" argument (fit-to-box based on ink alone) only holds for the outset case.
  2. background fills the box in this shape, hugs the ink in the others — unstated. Today's schema comment says so for pointText vs boxText; under the collapsed text type the brief never says which the pill does when w is a number. It must fill the authored box here.
  3. overflow's value set isn't in the brief. Text Foundation says the enum exists and applies only to the fixed box; the values aren't listed. Written as "shrink" here.
  4. A layer-level overflow collides in name with the container overflow: show | hide (Positioning → flowBox vocabulary). Same word, two different value sets, two different meanings. One of them should be renamed (fit for text, say).