Skip to content

Board Layout Implementation

This guide explains how Dataface's current board layout system is implemented today, why it behaves like a fixed layout system, and which design choices drive that behavior.

If you want the market/strategy companion to this doc, see the layout research report.

What This System Is

Today, Dataface boards are best understood as:

  • a structured authored layout system
  • compiled into a unified layout tree
  • sized before rendering
  • rendered into SVG-first output

That means the system is not doing browser-native responsive HTML layout in the way a typical React/CSS application would. It is much closer to:

  • "author a board"
  • "compile it into a known layout tree"
  • "calculate concrete item dimensions"
  • "render those dimensions into an SVG composition"

This is why the current experience feels stable and predictable across environments, but also why narrow containers often end up relying on overall scaling rather than true reflow.

The Mental Model

There are four major stages relevant to layout:

  1. YAML authoring Authors write rows, cols, grid, and tabs in board YAML.
  2. Layout normalization The compiler converts those different surface syntaxes into one internal Layout tree.
  3. Layout sizing The sizing pass assigns concrete width/height values to layout items.
  4. Layout rendering The renderer walks the sized tree and places items into SVG.

The key design choice is that layout is mostly resolved before final rendering, not delegated to the browser at the last moment.

Step 1: YAML Surface Area

The author-facing syntax lives in the board docs:

At this level, authors choose between:

  • rows
  • cols
  • grid
  • tabs

They can also nest boards recursively, attach titles/content/styles, and place charts or other boards at any level.

This YAML is intentionally simple. The complexity is pushed into normalization and sizing.

Step 2: Normalization to a Unified Layout Tree

Implementation entry point: dataface/core/compile/normalize_layout.py

This is one of the most important design choices in the current system.

The compiler does not keep separate downstream codepaths for raw YAML rows, cols, grid, and tabs. Instead, _build_unified_layout() converts them into a single internal Layout object with LayoutItem children.

Why this choice is good:

  • renderers can trust a consistent shape
  • sizing logic can run on one internal model
  • nested boards work recursively without special-case YAML handling everywhere
  • item references, inline charts, foreach expansion, tab metadata, and nested faces all resolve before render

This is the same pattern described in the Core Architecture doc: the compiler's job is to remove ambiguity so renderers can trust compiled types.

Step 3: Sizing Happens Before Render

Implementation entry point: dataface/core/compile/sizing.py

This is the second major design decision, and it explains most of the current behavior.

Dataface does data-aware sizing before layout rendering:

  • KPI charts get compact default heights
  • standard charts get larger default heights
  • tables can use actual row counts to determine height
  • markdown content is measured before final placement
  • nested boards inherit a bounded space and size their children within it

This is not CSS flexbox or CSS grid. It is an explicit sizing pass that writes dimensions onto layout items before the final renderers touch them.

Important sizing rules

From the current implementation:

  • In rows, items generally get full available width and content-appropriate height.
  • In cols, items divide available width and are normalized to a shared row height.
  • In grid, positions and spans become explicit cell-based geometry.
  • In tabs, only the active tab's contents are rendered in the SVG view.

The sizing module also encodes several product opinions:

  • charts have natural default heights by chart type
  • boards themselves do not add much implicit padding
  • the root face adds page padding
  • tables are special because row count affects real required height

This is a strong "known geometry first" philosophy, not an "ask the browser to figure it out later" philosophy.

Step 4: Rendering Trusts the Sized Layout

Implementation entry points:

  • dataface/core/render/renderer.py
  • dataface/core/render/faces.py
  • dataface/core/render/layouts.py

By the time layout renderers run, they largely trust the normalizer and sizing pass.

That shows up directly in the code:

  • render() calls calculate_data_aware_layout(...) before rendering
  • render_face_svg() computes the overall root drawing area
  • render_rows_layout() and render_cols_layout() use pre-calculated item sizes
  • comments in layouts.py explicitly say "Trust the normalizer"

This means the browser is not deciding:

  • how tall a row should be
  • how wide a column should be
  • where a grid item belongs
  • how a nested layout should reflow

Those decisions have already been made upstream.

Why It Feels Like a Fixed Layout System

The current design has several properties that together produce a fixed-layout feel:

1. Root dimensions are established up front

render_face_svg() starts with a root width and height, then subtracts page padding, title height, content height, and variable-control height to get the usable layout area.

2. Child items receive concrete geometry

Children are rendered with numeric widths, heights, and positions rather than semantic "grow/shrink/wrap" instructions.

3. Output is SVG-first

The HTML path wraps the SVG output rather than re-implementing the layout as native HTML/CSS layout. That preserves consistency, but it also means the authored composition remains the primary truth.

4. Layout adaptation is mostly compile/render logic, not browser reflow

If the surrounding viewport changes, the browser is not re-computing a fresh semantic layout tree. It is mostly displaying the already-composed result.

Put differently: the system is optimized for deterministic composition, not continuous responsive reflow.

Why It Was Built This Way

There are good reasons for the current design.

Predictability

The same authored board produces a stable composition. That is valuable for:

  • visual QA
  • screenshots and exports
  • examples and documentation
  • dashboard-builder mental model
  • "single screen" dashboard design

Unified pipeline

The same compiled structure can feed:

  • SVG
  • HTML wrapper output
  • PNG/PDF export
  • terminal rendering via parallel layout concepts

Content-aware sizing

Pure browser layout would not automatically know that:

  • KPI cards can be short
  • markdown content needs measured height
  • tables need row-count-aware height

The sizing pass lets Dataface encode dashboard-specific heuristics directly.

Recursive nesting

Nested boards become much easier to reason about when each level receives a concrete box and sizes its children within that box.

Tradeoffs of the Current Design

The same choices that make the system predictable also create the behavior behind the current research problem.

Benefits

  • stable authored layouts
  • easy to reason about composition
  • strong support for at-a-glance dashboards
  • consistent export behavior
  • deterministic recursive layout

Costs

  • limited browser-native responsiveness
  • narrow containers often depend on whole-layout scaling
  • text legibility problems show up before composition breaks
  • embeds and split-pane environments are harder
  • different content types cannot adapt independently unless explicitly implemented

This is exactly why the companion layout research report recommends a hybrid evolution rather than a full replacement.

Current Design Choices, Explained

Choice: one unified internal layout model

Why:

  • simpler render pipeline
  • fewer downstream special cases
  • recursive nesting works cleanly

Consequence:

  • layout behavior is centralized and easier to evolve

Choice: render-time sizing, not compile-time-only sizing

Why:

  • some content, especially tables and markdown, needs real measurements

Consequence:

  • output geometry can reflect data volume
  • layout is still explicit and deterministic once calculated

Choice: SVG-first rendering

Why:

  • consistent cross-format output
  • chart rendering already targets SVG well
  • easier export story

Consequence:

  • composition remains stable
  • browser-native responsive layout is not the default runtime behavior

Choice: root page padding, minimal board padding

Why:

  • keep board composition dense and dashboard-like

Consequence:

  • good information density
  • less slack space for adaptation in constrained containers

Choice: rows and cols encode simple strong defaults

Why:

  • authors can express dashboards quickly
  • layout rules stay understandable

Consequence:

  • the system is approachable
  • more advanced responsive behavior needs explicit future work rather than emerging naturally from HTML flow

Where to Read the Code

If you want to trace the current implementation directly, start here:

  • dataface/core/compile/normalize_layout.py
  • dataface/core/compile/sizing.py
  • dataface/core/render/renderer.py
  • dataface/core/render/faces.py
  • dataface/core/render/layouts.py
  • tests/core/test_layout_rendering.py

The Core Architecture doc is the best broader map of how those files fit together.

How This Connects to the Research Doc

This implementation guide explains what we built and why it behaves the way it does today.

The companion layout research report explains how the broader industry talks about these models, what other BI tools do, and what Dataface should probably do next.

They are intentionally paired:

  • this doc: current state, implementation, design rationale
  • research doc: terminology, market comparison, strategic recommendation

Bottom Line

Dataface's current board system is a compiled, sized, SVG-first layout engine with strong deterministic behavior. That is why it works well for stable, single-screen dashboards and export-friendly compositions.

It is also why it behaves more like a fixed canvas with calculated geometry than a browser-native responsive layout system.

That is not an accident or a bug in the implementation. It is the direct result of deliberate design choices in:

  • layout normalization
  • data-aware sizing
  • SVG-first rendering
  • recursive explicit geometry

If we want more responsive behavior, the right next step is not to pretend the current system is already responsive. The right next step is to evolve this architecture deliberately, with the tradeoffs in the companion research doc in mind.