# Rule: Use card/flex/div for Layout
`card` and `flex` (alias for `card`) use a CSS flexbox engine that auto-positions children. Use `card` for visual containers (background, border, shadow), `div` for invisible grouping and pure layout (no background, no border, no clipping).
## Scene = Implicit Flex Container
Every scene acts as an implicit full-screen flex container (`direction: column` by default). Children without `position` participate in flex flow automatically. Children with `position` are absolutely positioned.
You can customize the scene layout:
```json
{
"duration": 5.0,
"layout": {
"direction": "column",
"align_items": "center",
"justify_content": "center",
"gap": 24,
"padding": 40
},
"children": [
{ "type": "text", "content": "Centered title", "style": { "font-size": 64, "color": "#FFFFFF" } },
{ "type": "text", "content": "Subtitle below", "style": { "font-size": 32, "color": "#94A3B8" } }
]
}
```
## Card/Flex Patterns
Key patterns:
- **Horizontal row:** `"flex-direction": "row"` + `"gap"`
- **Vertical stack:** `"flex-direction": "column"` (default) + `"gap"`
- **Centered content:** `"align-items": "center"` + `"justify-content": "center"`
- **Auto-height:** `"style": { "width": 800, "height": "auto" }`
- **Grid:** `"display": "grid"` + `"grid-template-columns"`
Children flow in the flexbox. Use `positioned` container for absolute positioning.
**Grid sizing:** `height: "auto"` on a grid container sizes correctly to content — you don't need an explicit `height` just to avoid stretching. See [rules/grid-card-height.md](rules/grid-card-height.md).
## 23 component types have no intrinsic size — they need explicit `width`/`height`
Most components either measure their own content (`text`, `codeblock`, `counter`, `badge`, `table`, `terminal`, `caption`, `kbd`, `gradient_text`, `rich_text`) or get a computed fallback size from their own fields (`icon`-like shapes such as `avatar`, `divider`, `line`, `arrow`, `switch`, `slider`, `progress`, `list`, `timeline`, `notification`, `rating`, `qr_code`, `countdown`, `particle`, `cursor`, `connector`, `waveform`, `audio_spectrum`). The following **23 types have neither** (verified against `crates/rustmotion-components/src/box_builder.rs`'s `component_intrinsic` and `apply_intrinsic_overrides` — both are exhaustive `match`es and these fall through to their `_ => None` / `_ => {}` arms, and no component overrides `Painter::intrinsic_size` either):
`shape`, `image`, `icon`, `svg`, `video`, `gif`, `callout`, `chart`, `comparison`, `dot_map`, `gauge`, `heatmap`, `lottie`, `marquee`, `mockup`, `pill_nav`, `skeleton`, `sparkline`, `stat`, `stepper`, `tag_cloud`, `tooltip`, `treemap`
As a flex/grid child with no explicit `style.width`/`style.height`, any of these lays out at **0×0 and renders nothing** — not a smaller-than-expected box, no pixels at all. Confirmed by rendering: three `stat`s in a flex-row card with no explicit size produce a blank frame. `rustmotion validate` does not flag this (a 0×0 box doesn't overflow anything).
Always give these components explicit `style.width`/`style.height` (or a fixed size via their own dedicated `size` field where one exists, e.g. `qr_code`'s `size`) wherever they're a flow child of a `card`/`flex`/`grid` — not just when the parent has `height: "auto"`. If a component silently doesn't render, check this list before assuming a schema-field-placement bug (see [rules/component-field-placement.md](rules/component-field-placement.md) for that other, more common cause of invisible components).
**GOOD** (icon + text row):
```json
{
"type": "card",
"style": {
"width": 800,
"height": "auto",
"flex-direction": "row",
"align-items": "center",
"gap": 16,
"padding": 24,
"background": "#1E293B",
"border-radius": 16
},
"children": [
{ "type": "icon", "icon": "lucide:check-circle", "style": { "width": 48, "height": 48, "color": "#22C55E" } },
{ "type": "text", "content": "Feature enabled", "style": { "font-size": 32, "color": "#FFFFFF" } }
]
}
```