A dock_board’s arrangement is two independent things,
and it takes two constructor arguments to match:
-
views— the structure: which panels belong to each view (the global tabs across the top of the app) and the view’s display name. Onedock_viewper view. -
grids— the geometry: how each view’s panels are split, nested, tabbed and sized. Onedock_gridper view.
This vignette covers the grid syntax you use to describe geometry, how the two slots fit together at construction, and the patterns you’ll reach for most.
The big picture
A board stores structure and geometry as two aligned slots, keyed by
view id — read them with board_views() and
board_grids(). They are kept apart on purpose: membership
is owned by the update lifecycle (add / remove a block), geometry by the
client (you drag a sash, the board records it). A panel is
placed in a view when it is in both slots; the two are
reconciled only where the placement is read.
There is a third representation you should know the name of but will
rarely touch: dock_layout. It is
dockView’s own form — the
{grid, panels, activeGroup} payload the front end renders
and echoes back. It is the wire type at the client boundary, not
something you author with. Our model is dock_view +
dock_grid; dock_layout is what crosses to
dockView (?dock_layout).
The constructor is forgiving about which slot you fill:
| You pass | Result |
|---|---|
grids only |
one view per grid (id = the list name), members taken from the grid’s panels |
views only |
structure with no explicit geometry; each view renders a flat default over its members |
| both | aligned by id: views sets membership + names,
grids sets geometry |
| neither | a single default view (sidebar + main when there are extensions) |
So for a layout with real geometry you usually just pass
grids; the views come along for free. Reach for
views when you want a display name that differs from the
id, or a view without an explicit arrangement.
Grid syntax
You describe geometry with dock_grid() (and its helpers
panels() and group()). Two rules cover
everything:
-
List nesting alternates orientation.
dock_grid()’s top level lays its children out horizontally; one level oflist()nesting flips to vertical; another flips back, and so on. -
Character vectors create tabs. A vector of IDs
shares one DockView panel as tabs; a
list()of IDs is split into separate panels.
One panel
A single ID fills the whole view:
new_dock_board(
blocks = c(a = new_dataset_block()),
grids = list(Page = dock_grid("a"))
)┌────────────────────────────┐
│ Page [+] │
├────────────────────────────┤
│ │
│ a │
│ │
└────────────────────────────┘
The view is named after the grid’s list name (Page), and
its members are that grid’s panels (a).
Two panels side by side
Two top-level children → two columns split horizontally:
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
grids = list(Page = dock_grid("a", "b"))
)┌────────────────────────────┐
│ Page [+] │
├─────────────┬──────────────┤
│ │ │
│ a │ b │
│ │ │
└─────────────┴──────────────┘
Two panels stacked vertically
Wrap the children in one extra layer of
list() to introduce a vertical split:
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
grids = list(Page = dock_grid(list("a", "b")))
)┌────────────────────────────┐
│ Page [+] │
├────────────────────────────┤
│ a │
├────────────────────────────┤
│ b │
└────────────────────────────┘
The outer level still describes a horizontal split, but with a single
child that “split” is one full-width column. The inner
list("a", "b") is at depth 1, so it splits
vertically: a stacks on top of
b.
Tabs (multiple panels in one)
Use a character vector (not a list) to put several panels in one DockView panel as tabs:
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
grids = list(Page = dock_grid(c("a", "b")))
)┌────────────────────────────┐
│ Page [+] │
├────────────────────────────┤
│ ┌──────┐┌──────┐ │
│ │ a ││ b │ │
│ └──────┘└──────┘ │
│ │
│ (a is shown, b is a tab) │
│ │
└────────────────────────────┘
dock_grid(list("a", "b")) (panels split) and
dock_grid(c("a", "b")) (panels tabbed) read almost the same
but produce very different UIs — the list/vector distinction flips
between split a panel and tabify a panel.
Nested grids
Combine the two rules to build any arrangement.
Two columns, the right one stacked
new_dock_board(
blocks = c(
a = new_dataset_block(),
b = new_head_block(),
c = new_head_block()
),
grids = list(Page = dock_grid("a", list("b", "c")))
)┌────────────────────────────┐
│ Page [+] │
├─────────────┬──────────────┤
│ │ b │
│ a ├──────────────┤
│ │ c │
└─────────────┴──────────────┘
Two columns, both stacked
new_dock_board(
blocks = c(
a = new_dataset_block(),
b = new_head_block(),
c = new_head_block(),
d = new_head_block()
),
grids = list(Page = dock_grid(list("a", "b"), list("c", "d")))
)┌────────────────────────────┐
│ Page [+] │
├─────────────┬──────────────┤
│ a │ c │
├─────────────┼──────────────┤
│ b │ d │
└─────────────┴──────────────┘
Three rows in one column
A third level of nesting flips back to horizontal inside the vertical stack — a row holding two panels side by side:
new_dock_board(
blocks = c(
a = new_dataset_block(),
b = new_head_block(),
c = new_head_block()
),
grids = list(Page = dock_grid(list("a", list("b", "c"))))
)┌────────────────────────────┐
│ Page [+] │
├────────────────────────────┤
│ a │
├─────────────┬──────────────┤
│ b │ c │
└─────────────┴──────────────┘
Sidebar + tabs
A common dashboard shape: a narrow column on the left holding an extension, a tabbed panel on the right with several blocks:
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
extensions = new_edit_board_extension(),
grids = list(
Page = dock_grid("edit_board", c("a", "b"))
)
)┌────────────────────────────┐
│ Page [+] │
├─────────┬──────────────────┤
│ │ ┌────┐┌────┐ │
│ edit │ │ a ││ b │ │
│ │ └────┘└────┘ │
│ │ │
│ │ (tabs) │
└─────────┴──────────────────┘
This is also the shape new_dock_board() gives you by
default when both blocks and extensions are present, and you pass
neither slot: extensions on the left, blocks on the right.
Multiple views (pages)
Give grids more than one entry; each list name becomes a
separate page in the view-nav dropdown, and each becomes its own
view:
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
extensions = new_edit_board_extension(),
grids = list(
Analysis = dock_grid("a", "b"),
Editor = dock_grid("edit_board")
)
)Analysis view (active by default — the first view
wins):
┌────────────────────────────┐
│ Analysis [+] ← view nav │
├────────────────────────────┤
│ a │ b │
└────────────────────────────┘
Switching to Editor via the dropdown:
┌────────────────────────────┐
│ Editor [+] │
├────────────────────────────┤
│ │
│ edit │
│ │
└────────────────────────────┘
Each grid follows exactly the same syntax as the single-view form: character vectors for tabs, nested lists for splits.
Display names distinct from the id
A grid’s list name is the view id, and it doubles as the
display label. When you want a label that differs from the id — or a
view with no geometry at all — fill the views slot too. A
dock_view() carries the membership and the display name;
align it to the grid by id:
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
views = list(
main = dock_view(c("a", "b"), name = "Analysis")
),
grids = list(
main = dock_grid("a", "b")
)
)The nav shows Analysis; the stable id
main is what active, renames and updates
address.
Choosing the initially active view
The first view is active by default. To start elsewhere, name it with
new_dock_board(active = ), using its view id:
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
extensions = new_edit_board_extension(),
grids = list(
Analysis = dock_grid("a", "b"),
Editor = dock_grid("edit_board")
),
active = "Editor"
)┌────────────────────────────┐
│ Editor [+] ← starts here│
├────────────────────────────┤
│ edit │
└────────────────────────────┘
Which view is active is a single field on the views collection, keyed
by id — it belongs to the collection, not to any one view, so it is
always exactly one. Change it at runtime with
active_view(board) <- id, and read it back with
active_view(board); index
board_grids(board)[[id]] if you then need that view’s
geometry.
Views without geometry
Pass views alone (bare member vectors coerce to
dock_views) for structure with no explicit arrangement.
Each such view renders a flat default over its members until the client
arranges it (which the board then records):
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
views = list(
Analysis = c("a", "b"),
Empty = character()
)
)An empty view shows the same watermark prompt as an empty board, scoped to that tab.
Putting it all together
A pot-pourri: multiple views, nested grids, tabbed panels, an extension sidebar, and an explicit active view.
new_dock_board(
blocks = c(
raw = new_dataset_block(),
cleaned = new_head_block(),
summary = new_head_block(),
plot1 = new_scatter_block(),
plot2 = new_scatter_block()
),
extensions = new_edit_board_extension(),
links = list(
new_link("raw", "cleaned", "data"),
new_link("cleaned", "summary", "data"),
new_link("cleaned", "plot1", "data"),
new_link("cleaned", "plot2", "data")
),
grids = list(
Data = dock_grid(
"edit_board",
panels("raw", "cleaned", active = "cleaned"),
sizes = c(0.25, 0.75)
),
Analysis = dock_grid(
group("summary", "plot1", sizes = c(0.4, 0.6)),
"plot2",
sizes = c(0.55, 0.45)
),
Charts = dock_grid(panels("plot1", "plot2"))
),
active = "Charts"
)Three views; Charts is active, so the user lands there
first.
Charts (active on load): one tabbed panel,
plot1 shown, plot2 a tab.
┌────────────────────────────┐
│ Charts [+] │
├────────────────────────────┤
│ ┌───────┐┌───────┐ │
│ │ plot1 ││ plot2 │ │
│ └───────┘└───────┘ │
│ (plot1 shown, plot2 tab) │
└────────────────────────────┘
Data: a slim extension sidebar on the left, a wide
right column with raw / cleaned tabbed.
active = "cleaned" opens the cleaned tab;
sizes = c(0.25, 0.75) carves out the narrow sidebar.
┌────────────────────────────┐
│ Data [+] │
├──────┬─────────────────────┤
│ │┌─────┐┌─────────┐ │
│ edit ││ raw ││ cleaned │ │
│ │└─────┘└─────────┘ │
│ │ (cleaned shown) │
└──────┴─────────────────────┘
25% 75%
Analysis: two columns at 55/45. The left is a nested
group() with a 40/60 vertical split (summary over plot1);
the right is plot2.
┌────────────────────────────┐
│ Analysis [+] │
├─────────────┬──────────────┤
│ summary │ │ 40% / 55%
├─────────────┤ plot2 │
│ plot1 │ │ 60% / 45%
└─────────────┴──────────────┘
55% 45%
Custom split ratios and active tabs
The bare list-of-IDs form splits space evenly and opens the first tab. For non-default ratios or a non-default open tab, use the typed helpers:
-
dock_grid(..., sizes = c(...))— sizes parallel to the root children. They are relative; they need not sum to1(they are normalised for you). -
group(..., sizes = c(...))— the same, for a nested branch below the root. -
panels(..., active = "...")— a tabbed leaf with an explicit open tab.
A 30/70 split
sizes runs parallel to the children in ....
Two children split horizontally; two sizes make the split uneven:
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
grids = list(
Main = dock_grid("a", "b", sizes = c(0.3, 0.7))
)
)┌────────────────────────────┐
│ Main [+] │
├──────────┬─────────────────┤
│ │ │
│ a │ b │
│ │ │
└──────────┴─────────────────┘
30% 70%
Stacking with sizes
Same idea, vertical: orientation = "vertical" makes the
root split run top-to-bottom.
new_dock_board(
blocks = c(a = new_dataset_block(), b = new_head_block()),
grids = list(
Main = dock_grid("a", "b",
orientation = "vertical",
sizes = c(0.25, 0.75))
)
)┌────────────────────────────┐
│ Main [+] │
├────────────────────────────┤
│ a │ 25%
├────────────────────────────┤
│ │
│ b │ 75%
│ │
└────────────────────────────┘
Choosing the open tab
panels() builds a tabbed leaf. Without
active, the first ID opens — same as a bare character
vector. Pass active to open a different tab:
new_dock_board(
blocks = c(
a = new_dataset_block(),
b = new_head_block(),
c = new_head_block()
),
grids = list(
Main = dock_grid(panels("a", "b", "c", active = "b"))
)
)┌────────────────────────────┐
│ Main [+] │
├────────────────────────────┤
│ ┌────┐┌──────┐┌────┐ │
│ │ a ││ b ││ c │ │
│ └────┘└──────┘└────┘ │
│ ↑ │
│ b is open by default │
└────────────────────────────┘
Sizes inside a nested branch
group() is an inner list(...) with its own
sizes — use it for ratios on any non-root branch:
new_dock_board(
blocks = c(
a = new_dataset_block(),
b = new_head_block(),
c = new_head_block()
),
grids = list(
Main = dock_grid(
"a",
group("b", "c", sizes = c(0.6, 0.4)),
sizes = c(0.3, 0.7)
)
)
)┌────────────────────────────┐
│ Main [+] │
├────────┬───────────────────┤
│ │ b │ inner: 60% top
│ ├───────────────────┤
│ a │ c │ inner: 40% bottom
│ │ │
└────────┴───────────────────┘
30% 70%
Outer sizes = c(0.3, 0.7) controls the root split; inner
group(..., sizes = c(0.6, 0.4)) controls the right column’s
stack.
Cheat-sheet
Geometry (dock_grid(...)):
| Goal | Syntax |
|---|---|
| One panel | dock_grid("a") |
| Two side-by-side | dock_grid("a", "b") |
| Two stacked | dock_grid(list("a", "b")) |
| Tabbed panel | dock_grid(c("a", "b")) |
| Sidebar + main | dock_grid("ext", "main") |
| Two columns, both stacked | dock_grid(list("a", "b"), list("c", "d")) |
| Custom split ratio | dock_grid("a", "b", sizes = c(0.3, 0.7)) |
| Custom open tab | dock_grid(panels("a", "b", active = "b")) |
| Vertical top-level split | dock_grid("a", "b", orientation = "vertical") |
Board (new_dock_board(...)):
| Goal | Syntax |
|---|---|
| One view with geometry | grids = list(Page = dock_grid(...)) |
| Several views | grids = list(A = dock_grid(...), B = dock_grid(...)) |
| A display name != id | views = list(id = dock_view(members, name = "...")) |
| Structure, no geometry | views = list(Page = c("a", "b")) |
Start on view B
|
new_dock_board(grids = list(A = ..., B = ...), active = "B") |
| Empty starter view | views = list(Page = character()) |
Where to go from here
-
?dock_gridfor the geometry syntax (withpanels()andgroup()). -
?dock_viewfor the structure type, and?active_viewfor switching the active view at runtime. -
?dock_layoutfor the dockView boundary form, andas_dock_grid()/as_dock_layout()for the casts across it.