mugen

Primitives

The measurable vocabulary rows are built from.

A row is built from primitives — each one carries both an analytic measure and a DOM render, so mugen can compute its height and paint it from one description. A raw <div> has no measure, so it breaks the walk and throws in development. There are three built-ins — Text, VStack, HStack — plus definePrimitive(tag) to make your own from any HTML element, and Escape for content the walker shouldn't measure at all.

Every snippet below is type-checked — hover a primitive or prop to see its type, and the ✗ lines show the actual compiler error.

Text

The only primitive that calls pretext. Its measured height is layout(prepare(text, font, opts), width, lineHeight); its rendered CSS uses the identical font, line-height, and spacing, so the two agree. font/lineHeight fall back to the list defaults on <MugenVList>.

<>{.}</>; // inherits the list font
< ="700 18px Inter" ={24}>{.}</>;
< >{.}</>; // shrink-wrap to natural width
PropTypeNotes
childrenstringMust be a single string.
fontFontOverrides the list default (still measurable).
lineHeightnumberpx.
letterSpacingnumberpx, matches CSS.
whiteSpace'normal' | 'pre-wrap'pre-wrap keeps \n/tabs/spaces.
wordBreak'normal' | 'keep-all'
colorstring
shrinkbooleanShrink-wrap to the text's natural width (bubbles).

VStack / HStack

The two layout boxes. VStack sums child heights plus gaps; HStack takes the max child height and distributes width the way flexbox paints it — fixed-width siblings keep their width, the rest take their content width and shrink proportionally when the row overflows (matching flex: 0 1 auto). A custom primitive without a naturalWidth makes its row fall back to an equal split of the remainder.

// avatar | (author over body)
< ={12} ={12}>
  < ={40} ={40} ={{ : . }} />
  < ={2}>
    < ="600 15px Inter">{.}</>
    <>{.}</>
  </>
</>;

Layout is set through props, never CSS, because the props are the chrome the walker counts:

PropType
gapnumberSpace between children (px).
paddingnumberUniform padding (px).
widthnumberDeclared width — lays out a fixed sibling in an HStack.
heightnumberDeclared height — the box's height is this (replaces Fixed).
align / justifystringalign-items / justify-content.

A box with a height is a fixed box (an avatar, a spacer); a box with children derives its height from them.

definePrimitive(tag)

Make a measurable primitive backed by any HTML tag. It's a layout box (like VStack/HStack, which are themselves definePrimitive('div', …)) with the same chrome props — plus the tag's own attributes passed straight through, so a button is clickable and an a takes an href.

const  = ('button'); // vertical
const  = ('div', { : 'horizontal' });

< ={10} ={() => ()}>
  <>{.}</>
</>;

This replaces the old Pressable (use a button and its onClick) and Fixed (use width/height on a box).

Escape

The escape hatch: a fixed-size box that stays in the row's flow at a declared height (and optional width), but whose children are never walked — so they can be arbitrary non-primitive React. A shadcn/Radix tooltip (trigger and all), a chart, an <img>: anything with a known footprint drops in, and any floating content it portals never touches the row's layout. The walker reads height without recursing; the render pins the same height inline and clips overflow, so the children can't desync the row even if they misbehave.

< ={32} ={38} ="flex items-center justify-center">
  < /> {/* any React — hooks, raw divs, portals */}
</>;
PropTypeNotes
heightnumberRequired. Border-box height — authoritative for walk and paint.
widthnumberLays out as a fixed HStack sibling; reported as the natural width.
style / classNameunrestrictedThe interior is yours; the frame's height is pinned inline.

Inside an HStack whose other children wrap, give the Escape a width — without one its painted width is unknowable, so the row falls back to an equal split. See Constraints for the full contract.

Measurability is enforced by types

A primitive's style and className are restricted so its painted box can't diverge from the box the walker computes. Padding, margin, gap, and sizing are owned by props — setting them via raw CSS or a utility class is a type error (hover the ✗ lines):

< ={{ padding: 8 }} />; // ✗ use the `padding` prop
Object literal may only specify known properties, and 'padding' does not exist in type 'MeasurableStyle'.
< className="p-4 gap-2" />; // ✗ spacing utilities break measurement
Type '"p-4 gap-2"' is not assignable to type '"mugen: spacing/sizing utility classes (p-, m-, gap-, w-, h-, …) break analytic measurement — set padding/gap/width/height via primitive props instead"'.
< ={8} ={2} />; // ✓ < ="rounded-full" />; // ✓ visual-only classes are fine

This is the type-system replacement for the old lint rule: the editor stops you before a height can ever drift.

On this page