Board Sizing¶
Dataface uses a three-layer sizing model. Understanding which layer owns what prevents most sizing surprises.
The Three Layers¶
| Layer | Where | What it controls |
|---|---|---|
| Layout tile | rows: / cols: / grid: entry |
The slot the chart renders into |
| Chart root | charts.<name>.width / height |
Preferred width and fixed height requests from the chart itself |
| Chart style | charts.<name>.style.aspect_ratio |
Aspect-ratio-derived height for this chart |
| Theme default | style.charts.aspect_ratio in the face/theme cascade |
Global fallback |
Priority¶
- An explicit
height:on a layout tile always wins. - If the tile has no explicit height, chart-root
height:wins. - Chart-style
aspect_ratio:is used next (computesheight = width / aspect_ratio). - The theme default
style.charts.aspect_ratiois the final fallback.
The layout owns each chart's final width. Chart-root width: contributes to the dashboard's intrinsic width, but does not force the final slot width.
Default Behavior¶
Width Rule¶
Dataface measures the layout's preferred width recursively:
- Rows and tabs use their widest child.
- Columns add their child widths and gaps.
- Grids derive track widths from each item's span.
- Nested dashboards repeat the same calculation.
The rendered dashboard shrinks to that intrinsic width and never exceeds style.board.width, which acts as the maximum. A dashboard with no charts keeps the maximum width.
That intrinsic width sets the board's proportions, not its final on-screen size. Viewed as an HTML page — dft serve, dft render --format html — the board scales to fill the window width and keeps its aspect ratio, so these measurements govern relative density (how much room each chart gets, how large type reads against its plot) rather than pixels in the browser.
After that outer width is chosen, existing layout allocation rules still apply. For example, items in a columns layout split the available width evenly:
- In a cols layout with 2 items, each gets 50%
- In a cols layout with 3 items, each gets 33.3%
Height Rule¶
Heights are derived from width ÷ aspect ratio for plot-style charts (bar, line, area, etc.).
The global default aspect ratio is 1.5 (3:2), set in the base theme under style.charts.aspect_ratio. Override it in any face via style.charts.aspect_ratio or at the individual chart level with aspect_ratio:.
Some chart types override this — for example, pie and arc default to 1.0 (square), and map types default to 1.2.
Exceptions — chart-root height is ignored for these types:
- KPI — fixed compact height (renderer owns sizing)
- Table — data-driven height based on actual row count
- Callout — fixed compact height
- Spark bar — fixed height
The aspect-ratio-derived height is clamped between min_height and max_height. The face/theme defaults are style.charts.min_height (150px) and style.charts.max_height (400px). Individual charts can override these clamps with per-chart style: {min_height: ..., max_height: ...} — see Chart height clamps below.
In a cols layout, all items share the same height (the tallest item's height).
Unequal Widths with Nesting¶
Use nested lists as a shorthand for nested boards. A nested list automatically inherits the layout type (rows or cols) of its parent.
50% / 25% / 25% Split¶
To make the first chart take up half the width (50%) and the next two share the remaining half (25% each), nest them in a list:
source: examples_db
title: "Asymmetric Layout"
queries:
sales_by_category:
sql: |
SELECT category, SUM(revenue) AS revenue, SUM(units_sold) AS units_sold FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
sales_summary:
sql: |
SELECT SUM(revenue) AS revenue, SUM(units_sold) AS units_sold, COUNT(DISTINCT category) AS category_count FROM ecommerce_orders
charts:
main_chart:
query: sales_by_category
type: bar
title: "Main Chart (50%)"
x: category
y: revenue
kpi1:
query: sales_summary
type: kpi
label: "Side Chart 1 (25%)"
value: revenue
kpi2:
query: sales_summary
type: kpi
label: "Side Chart 2 (25%)"
value: units_sold
# 50% / 25% / 25% split using nested cols
cols:
- main_chart
- cols:
- kpi1
- kpi2
Complex Nesting¶
source: examples_db
title: "Complex Dashboard"
queries:
sales_summary:
sql: |
SELECT SUM(revenue) AS revenue, SUM(units_sold) AS units_sold, COUNT(DISTINCT category) AS category_count FROM ecommerce_orders
sales_by_category:
sql: |
SELECT category, SUM(revenue) AS revenue, SUM(units_sold) AS units_sold FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
sales_by_product:
sql: |
SELECT product, category, SUM(revenue) AS revenue, SUM(units_sold) AS units_sold FROM ecommerce_orders GROUP BY product, category ORDER BY revenue DESC
sales_by_date_category:
sql: |
SELECT date, category, SUM(revenue) AS revenue, SUM(units_sold) AS units_sold FROM ecommerce_orders GROUP BY date, category ORDER BY date, category
charts:
kpi1:
query: sales_summary
type: kpi
label: "Revenue"
value: revenue
kpi2:
query: sales_summary
type: kpi
label: "Units Sold"
value: units_sold
mini1:
query: sales_by_category
type: bar
title: "Mini Chart 1"
x: category
y: revenue
mini2:
query: sales_by_product
type: bar
title: "Mini Chart 2"
x: product
y: units_sold
main:
query: sales_by_date_category
type: line
title: "Main Analysis"
x: date
y: revenue
color: category
rows:
- cols:
- kpi1
- kpi2
- cols:
- mini1
- mini2
- main
Authoring Chart Size¶
Chart root holds the two fixed-geometry fields:
height: <px>— exact pixel height. Wins overaspect_ratioand theme defaults. Bypassesmin_height/max_heightclamps.width: <px>— preferred width used to measure the dashboard. Does not override the final layout slot width.
style: holds aspect-ratio sizing fields (and all paint — colors, fonts, marks):
style.aspect_ratio: <float>— shape without a fixed pixel size.height = width / aspect_ratio.style.min_height: <px>— floor for the computed height.style.max_height: <px>— ceiling for the computed height.
height and width belong at chart root, not in style: — putting them there raises a validation error.
Choosing between chart-root height and a layout tile height¶
# Chart-root height — the chart requests 400px; # the layout honours it unless the tile has its own height. charts: inventory_overview: query: inventory type: bar x: category y: count height: 400 # Layout tile height — the slot is fixed at 600px regardless of the chart inside. rows: - height: 600 rows: - inventory_overview
Use chart-root height when the chart itself has a natural size independent of its context (e.g. a detail chart that always needs 400px to be readable).
Use a layout tile height when you want the tile — and everything in that row — to be a fixed size (e.g. a KPI row that must be 120px tall).
Rejected shape¶
# REJECTED — height/width under style: is not supported charts: bad: type: bar style: height: 400 # ← raises a validation error; move height to chart root
Explicit Sizing¶
Chart height¶
Set height on a chart to fix its pixel height explicitly. This bypasses aspect-ratio sizing and ignores min_height / max_height clamps.
charts: revenue: type: bar query: revenue_by_month x: month y: revenue height: 500 # fixed at 500px
Set aspect_ratio to control the shape without fixing an exact pixel size:
charts: revenue: type: bar query: revenue_by_month x: month y: revenue style: aspect_ratio: 2.5 # wider-than-default; height = width / 2.5
Chart height clamps¶
min_height and max_height cap the aspect-ratio-derived height. Face/theme defaults cascade from style.charts.min_height and style.charts.max_height. Override them per chart under style::
charts: stock_levels: type: bar query: stock_levels x: product_name y: stock_quantity style: aspect_ratio: 1.5 min_height: 80 # floor for this chart only; overrides face/theme default max_height: 200 # ceiling for this chart only; overrides face/theme default
Explicit chart.height bypasses min_height / max_height — when you set an exact pixel height, the clamps do not apply.
Height on Rows¶
Set explicit height on row items:
rows: - height: "120px" cols: - kpi1 - kpi2 - kpi3 - main_chart # Takes remaining height
Grid Sizing¶
In grid layouts, use width and height on items:
grid: columns: 24 items: - item: kpi1 width: 8 # 8 of 24 columns - item: kpi2 width: 8 - item: main_chart width: 16 height: 2 # 2 rows tall - item: sidebar width: 8 height: 2
Sizing Patterns¶
KPI Row + Main Chart¶
rows: - height: "100px" cols: [kpi1, kpi2, kpi3] - main_chart
Sidebar Layout¶
cols: - cols: # 66% main content - chart1 - chart2 - sidebar_chart # 33% sidebar
Header + Body + Footer¶
rows: - height: "80px" text: "# Dashboard Title" - main_content # Flexible middle - height: "60px" text: "Footer text"
Configuration¶
Theme defaults (face/theme cascade)¶
These keys set the theme default layer — they apply to every chart that does not provide a chart-root override.
| Key | Default | Description |
|---|---|---|
style.charts.aspect_ratio |
1.5 |
Global width:height ratio for plot-style charts |
style.charts.min_height |
150 |
Minimum chart height (px) when aspect-ratio drives sizing |
style.charts.max_height |
400 |
Maximum chart height (px) when aspect-ratio drives sizing |
style.charts.<type>.aspect_ratio |
— | Per-type override (e.g. pie: 1.0, map: 1.2) |
height and width cannot be set in the theme — they are per-chart explicit overrides only (see below).
Chart-root fields (per chart, under charts.<name>:)¶
| Field | Description |
|---|---|
height |
Exact pixel height. Wins over aspect_ratio and theme. Bypasses min_height/max_height. |
width |
Preferred width in pixels. Contributes to the dashboard's intrinsic width; the chart still fills its final layout slot. |
Per-chart style fields (under charts.<name>.style: or in the theme under style.charts.*)¶
| Field | Description |
|---|---|
aspect_ratio |
Shape without a fixed pixel size. height = width / aspect_ratio. |
min_height |
Floor for this chart only; overrides theme style.charts.min_height. |
max_height |
Ceiling for this chart only; overrides theme style.charts.max_height. |
aspect_ratio, min_height, and max_height are valid both at chart level (style: {aspect_ratio: 2.0}) and in the theme (style.charts.aspect_ratio). height and width are chart-root-only — they are not valid under style:.
Card Gap¶
By default, cards (charts and content blocks) render edge-to-edge — no gap
between them. Set card_gap: true at the board root to add spacing between
every card on the board:
title: "Spaced-Out Dashboard" card_gap: true rows: - revenue - orders
The actual pixel value comes from the theme (style.board.card_gap), not from
the board — card_gap is a per-board on/off switch, the theme owns how big the
gap is. card_gap is root-only: setting it on a nested board raises a
validation error, because spacing is a whole-board layout decision, not a
per-section one.
Related¶
- Boards Overview - Basic board structure
- Layouts - Layout types
- Examples - Complex layout patterns