# Layout

Do not shadow Node methods, do not flex-size to unmeasured text, match anchors to coordinates, and keep sizes proportional to the frame.

Layout uses Flexbox on a hidden DOM tree. Text size is measured before the webfont loads, and offscreen before the node joins a document. Most broken custom elements come from that.

Flex is for placement inside a container that already has an explicit `width` and `height`. It is not for measuring content.

## Do not shadow `Node` members

Never name a class field after a `Node` method or property. The field overwrites the real member. The element compiles and then draws nothing, or crashes at render.

Do not use these names for `@signal` fields:

`draw`, `render`, `size`, `position`, `scale`, `opacity`, `cache`, `key`, `children`, `add`, `remove`, `view`, `parent`, `clip`, `filters`, `x`, `y`, `width`, `height`, `fill`, `stroke`, `rotation`, `zIndex`

Prefix intent: `drawProgress`, `revealProgress`, `textSize`.

## Do not flex-size to text you drew

A `Rect` that hugs a `Txt` is measured with a fallback font. After the real font loads, the label clips.

Compute width from the string instead:

```ts
const CHARACTER_WIDTH_RATIO = 0.55; // ~0.62 for monospace
const textWidth = this.label().length * this.textSize() * CHARACTER_WIDTH_RATIO;
const capWidth = Math.max(this.capHeight(), textWidth + this.textSize());
```

Give that number to the `Rect` as `width`. Then place children with `layout` inside that fixed box.

Set an explicit `width` on wrapping text and use `textWrap`. Do not rely on a manual newline inside `Txt`.

## Anchors and coordinates

Vidova uses a center origin. X increases to the right. Y increases downward. Do not treat the origin as top-left.

`offsetX` of `-1` means x is the left edge. Passing a center-based x then shifts the node by half its width. Derive both from one helper: an anchor of -1, 0, or 1, and the matching coordinate.

## Sizes follow the frame

Hard-coded pixel margins that look right at 1920x1080 are wrong at the 480x270 thumbnail. Express insets as a fraction of the frame.

Hidden means hidden in every dimension. Setting `visible` to `false` is not enough if a stroked rect still draws. Zero opacity or size as well.

## `Code` letter spacing

`Code` reads letter spacing from computed CSS, not from its signal. That CSS is `normal` until the element joins the document. Set `letterSpacing` to `0` on every `Code` node.

Code text uses `fontFamily="Fira Code Variable"`. Do not pass a CSS font stack. The family string must match a bundled font exactly. UI text uses `Inter Variable`.

## `layout` on a root

`layout` on a container with explicit `width` and `height` is safe. `layout` where the container size falls out of its children is where measurement bites.

```tsx
<Rect layout width={480} height={72} gap={16} alignItems="center">
  <Circle width={24} height={24} />
  <Txt text={() => this.label()} fontSize={28} fontFamily="Inter Variable" />
</Rect>
```

The `Rect` has a fixed size. The `Txt` does not decide the box.

## `layout={false}` overlays; it does not stack

Children with `layout={false}` **do not occupy flex space**. They overlay siblings. The parent size ignores them.

Use `layout={false}` only for overlay animation: a measured in-flow sibling sets the box, and a second copy with `layout={false}` moves or fades on top of it.

Stacked cards, counters, labels, and image-plus-chrome must stay **in the layout flow** with explicit `width` and `height`. If you mark the visible stack `layout={false}`, it draws on top of the previous child and the parent collapses.

```tsx
<Rect layout width={480} height={220} direction="column" gap={12} alignItems="center">
  <Rect width={480} height={160} fill="#111" />
  <Txt text={() => this.label()} fontSize={28} fontFamily="Inter Variable" />
</Rect>
```

Both children participate in flex. The photo and the caption take height.

Signal bindings for labels: [Signals](/docs/custom-elements/signals). Intro vs timeline playback: [Animation](/docs/custom-elements/animation). Asset images inside a stack: [Inputs](/docs/custom-elements/inputs).
