blockr design system

The system as decided on 2026-09-23, drawn at real size in Open Sans. Every colour, size and radius on this page comes from blockr-tokens.css and blockr-tokens-dark.css, so Dark switches every part at once. The written rules are in the design system article, under the same sections and anchors. Everything is live: open the gears, hover the icons, click the words, pills and boxes.

Two blocks, as decided

A transform block holds the fields that define it on its face; an output block holds its output and a sentence with live words. Both keep the rest behind the gear, which opens the tray in flow and closes on the gear or Escape. rules: design-system#block

Subset
Order
Order by
Conditions
6 7 8
Columns and rows
Keep columns
Keep rows that match
Maximum number of rows
Enter ↵
OzoneintSolar.RintWinddblMonthint
17129113.86
21352694.17
3492489.27
464NA7.48
37 of 153 rows · 4 of 6 columns
Ozone by month

Mean ozone by month

by , for

Mapping
Category
Value
Aggregate
Colour by
Presentation
Orientation
Sort
Height
Enter ↵
0
20
40
60
23.6
May
Mean ozone23.6
Days26
29.4
June
Mean ozone29.4
Days9
59.1
July
Mean ozone59.1
Days26
60.0
August
Mean ozone60.0
Days26
31.4
September
Mean ozone31.4
Days29
MayJunJulAugSep

Days with missing ozone are left out.

116 of 153 rows

1Principles

Each principle below is a rule the later sections apply. rules: design-system#principles

  • Components read meaning tokens (--blockr-color-text-muted), never palette steps (--blockr-grey-500) and never a hex. A theme changes a few palette values and dark mode is a second mapping of the same ramps.
  • Text has three colours: default for content and headings, muted for what describes it, disabled for disabled controls only.
  • A transform block's face holds the fields that define it and its gear holds the rest. An output block's face holds the output and its sentence. Simplified mode removes the gear button and nothing else.
  • A setting applies the moment it changes. There is no Apply button.
  • Settings open in flow, in the gear tray. Choices and actions open in a menu on click. Names show in a tooltip on hover. There are no popovers.
  • Accent means "on" or "the main action": the tinted main button, a ticked box, a picked segment, a live word, an active filter. A view has at most one main button.
  • Shape tells what can be acted on. A badge, which is only read, has round ends. Tags, pills and buttons have 4px corners.
  • Sizes come from one ladder: controls 42px, toolbar buttons 30px, header tools 26px; fields sit in one grid of about 130px columns.

2Foundations

Palette tokens are read by other tokens only; components read meaning tokens, named --blockr-color-<text|bg|border>-<role>. Values below are read from the token file at load. rules: design-system#foundations

Colour: the palette

A ten-step grey ramp, an accent ramp that points at blue unless a theme overrides it, and small red, amber and green ramps for status. No UI colour outside the palette.

Three text levels

The level follows the job the text does. Muted text does not sit on the hover or selected background: on a hovered row it turns default, on a selected row accent.

Default
text-default
  • values, body text, menu rows, tooltip text
  • headings: block title, output title, table column names
  • hover state of a muted icon
Muted
text-muted
  • labels, section titles, placeholders, hints
  • subtitle, caption, status line, counts, meta
  • column types, row numbers, NA; icons at rest
Disabled
text-disabled
  • disabled controls only
  • below the text contrast minimum, which WCAG allows only here

Surfaces, borders, status, focus

Layers sit on each other: the board canvas (bg-page), a panel (bg-surface), anything floating (bg-raised). Inside a layer: a quiet fill (bg-subtle), a field (bg-field), hover and selected. Borders are soft (border-default). A field's focus is the accent border plus the soft ring; everything else takes a 2px accent outline on keyboard focus (Tab through this page).

bg-page
bg-surface
bg-subtle
bg-field
bg-hover
bg-raised, shadow-md

Text

Borders and focus

Backgrounds

Dark scheme

Keyed off data-bs-theme="dark", the attribute bslib writes. The grey and blue ramps are restated by position, page, surface and raised get their own values, and status colours turn lighter. The gear tray takes the page colour in dark. Use the switch at the top; every part of this page follows.

Theming: the accent ramp

A theme overrides the five accent steps (and optionally the greys, a radius and a face). Every accent-derived token follows: main button, ticked box, picked segment, live word, count.

Default accent (blue)

by Added3

A theme's accent (teal, five values)

by Added3

Type

Open Sans, set once through --bs-body-font-family. Five sizes, three weights, one monospace stack.

Block title (dock header)Subsetxl · 20px · 600 Output titleMean ozone by monthlg · 16px · 600 Body, values, menu rowsairquality · 153 rows · Ozone, Solar.R, Windbase · 14px · 400 Subtitle, sentenceMean Ozone by Month, for May, June, Julysm · 13px · 400 · muted Labels, caption, status, tooltipsNumber of rows · 116 of 153 rowsxs · 12px · 400 · muted Section titlePresentationxs · 12px · 600 · caps · 0.05em BadgeCategorical11px · 500 Chart canvas textMay · Jun · Jul · 0 · 20 · 40mark-font-size · 11px Codefilter(Ozone > 40, !is.na(Wind))font-mono · 13px

Shape and size

Four radii and the capsule. Three control heights: 42px for fields, rows and buttons beside them, 30px in toolbars and dialogs, 26px in a header row next to the gear.

sm 4 · tags, pills, xs buttons, gear, menu rows
md 6 · 30px buttons, messages
lg 8 · fields, rows, menus, tray, tooltips
xl 12 · dock panels
pill · badges, counts
Ozone
42 · control-h
30 · control-h-sm
26 · control-h-xs
Ozone≥int24 tag, pill · 18 badge

Elevation and motion

One shadow carries the floating layer: shadow-md on menus, tooltips and the chart tooltip. The tray is in flow and has none. In dark the lighter bg-raised separates a menu from the surface. Hover and state changes take --blockr-transition (0.15s); the tray slides over 0.22s.

shadow-sm
shadow-md · menus, tooltips
shadow-lg

Data marks

A bar's free end takes mark-radius (2px); the end on the axis stays square. Text on a chart canvas is 11px. A mark that supports a choice, such as the bars in a crossfilter list, is drawn at the supporting weight (0.6). Chart and series colours come from blockr.theme; this page draws them in the accent.

Ozone Solar.R July (picker) June (picker)

Block category colours

Fixed, outside the tokens: one Okabe-Ito colour per category from blk_color() in blockr.dock. The same under every theme and in dark.

input #0072B2
transform #009E73
structured #56B4E9
plot #E69F00
table #CC79A7
model #F0E442
output #D55E00
utility #CCCCCC
uncategorised #999999

3The block

Below the dock header a block has a header row, the gear tray, the controls on its face and its output (body, caption, status line). Its output title and sentence belong to the output and export with it. rules: design-system#block

Anatomy

An output block with its tray open. The transform block above shows the fourth part, controls on the face.

1
Ozone by month
2

Mean ozone by month

Mean by

3
Presentation
Sort
Height
Enter ↵
4
0
20
40
60
23.6
29.4
59.1
60.0
31.4
MayJunJulAugSep
5

Days with missing ozone are left out.

6
116 of 153 rows
1Dock header. Category icon, block title (20px, 600) and a plain-text subtitle. The block's package, description and "Copy block ID" head its "…" menu.
2Header row. The output title (16px, 600) shares the row with the tools and wraps in the space they leave. Its sentence is the subtitle (13px, muted). Tools run left to right, gear last, all 26px.
3Gear tray. In flow under the header row: bg-subtle (the page colour in dark), no border, white fields, a beak towards the gear, no heading. Sections are a section title and 24px of space. Fields use the grid.
4Body. The chart or table. A transform block has its controls on the face above the output instead (see Subset above).
5Caption. 12px, muted.
6Status line. 12px, muted: row counts, filter state, timing.

The gear tray

The sentence and its slots

The sentence on a block's face looks as it will in print. The author writes it in the #| subtitle: template and decides the line breaks with \n; blockr generates nothing and adds no breaks. A live word is the only way an output block's argument reaches its face.

AE summary by worst grade

Rows: Grades:

  • A live word is accent text with a dashed underline, solid on hover. A click opens its list under the word.
  • A slot with many values shows the first three, then "+N more" inside the same word. Exports print the same text.
  • Unset settings show as "+" offers on their own line under the sentence, three at most; past three the last reads "More settings" and opens the gear. Offers are not printed.
  • A clause in [ ... ] leaves with its value; the sentence never prints "(none)".

Simplified mode

Simplified mode removes the gear button and nothing else. What stays on the face stays live for the reader.

Mean ozone by month

Mean by

The block's mark at each size

A tinted square in the category colour with the glyph in that colour: 28px in the header, 24px in list rows (with the package as a badge), 16px in a tab. The header mark's tooltip names the block type and package.

header 28
list 24
tab 16
Import Datablockr.io
Filter Rowsblockr.dplyr
Chartblockr.viz

Block status, messages, computing

A block that cannot run yet shows an amber dot on its mark and says so in its status line. An error or a warning for the whole block is a message above the output. Past 300ms of computing any block dims its output and runs a clock under it. Press Run. The block that needs you has a solid amber dot; the blocks below it, waiting for it, have amber rings.

Unset: set this block's inputsWaiting for a data inputStale: inputs changedFailed
Read data
Error. Column "Ozone2" not found in the data.
Set this block's inputs
Ozone by month

Bare mode (later)

A board option for dashboards: the card has no header; the tab carries the mark and the name, the actions sit right of the tabs, a double-click on the tab renames. It waits on dockViewR's tab components.

Ozone above 40 ✕
Column
Ozone

4Layout

One grid of about 130px columns, 8, 6, 4, 2 or 1 by panel width. A number or checkbox takes one column, a select or text field two, tags or an expression the whole row. rules: design-system#layout

The grid and field sizes

Set the panel width, or drag the panel's bottom-right corner. Columns line up across sections at every width; a short section leaves room on the right. "Show columns" draws the tracks and each field's size.

Panel width
Mapping
Category
Value
Aggregate
Title
Enter ↵
Presentation
Bins
Enter ↵
Height
Enter ↵
Palette
Filter
Expression
Enter ↵

Spacing, section titles, field labels

Presentation
Sort
Aggregate
BetweenSpace
field label and its control4px
grid rows · grid columns16px · 12px
section title and its fields10px
one section and the next24px, no rule
list rows6px
row handle's padding (left of the row)at least 14px

Rows

A row in an add-and-reorder list is 42px, the height of a field, border included. Its content starts on the same edge as the label above it (the dashed line). The handle appears in the container's left padding on hover or focus; remove appears on the right and turns red on hover. The fold arrow stays visible. Hover a row.

Columns
Keep, in this order
Ozoneint
Solar.Rint
Winddbl

The second row is drawn hovered.

5Controls

Fields are 42px on the field background with a soft border; focus is the accent border and the ring. On/off is a bare checkbox; two or three short fixed values a segmented control; four or more, or values from the data, a Select; several from a list, tags. rules: design-system#controls

Text and number inputs

Text and numbers commit on Enter or blur; while a field holds an uncommitted change the Enter ↵ button shows, and Escape reverts. Number fields keep type=number and hide the arrows. Type in any of them.

Empty, with placeholder
Enter ↵
Leave empty to keep the names
Filled
Enter ↵
Changed, not committed
Enter ↵
Number
Enter ↵
Focused
Group by *
Required, still empty
Number of rows
Enter ↵
Must be a positive number
Disabled

Select

A select is a field with a caret. Its dropdown takes the field's width (at least 190px), shows a filter box above 8 options, and marks the current pick at weight 600. Several from a list is one multi control with tags; the options stay hidden until it opens. Only the hovered tag shows its ×.

Value
Colour by (12 options)
Package
datasets
Keep columns

Checkbox

The bare box and its words, with no field shell and no empty label row. In the grid it sits on the bottom line of its row and takes one column; on a row of its own it takes only its own height. Click them.

Height
Enter ↵
  • On is the accent fill with a white tick; off is the strong border; disabled is half opacity with disabled text.
  • The same checkbox is used in the tray, on the face and in the board options. There is no switch.
  • Space toggles; focus shows the ring.

Segmented control

For a fixed set of two or three short values. Both values stay in view and the pick reads without a click. It fills a grid cell like a field; three segments need about 240px, so they take two columns. The pick is the main-button tint.

From
Facet layout
Missing values

A pill that opens a menu

Four or more values inside a row (a filter operator, a join key operator) are a pill with a caret that opens a menu anchored on it. It is 24px inside a 42px row, keeps the row's shape and puts every value one click away. Click the pills.

Builder or code

The one mode switch is a pressed icon button at the 26px tool size. It is a tool, so it stays out of the field grid; its tooltip names the other state and it sets aria-pressed. Click </>.

Formula
Ozone~Temp+Wind
Enter ↵

Renaming in place

Double-click a name (or pick Rename in its menu): the text becomes a field at its own size. Enter commits, Escape restores, a click elsewhere commits; an empty or duplicate name is refused in place. Try both.

Ozone above 40
Measurements
Complete cases
Mean ozone

6Actions

Four kinds of button in three sizes; the main button is the tinted accent and there is no solid accent button. Icon buttons are quiet and carry a tooltip. rules: design-system#actions

Buttons

42px beside inputs, 30px in toolbars and dialogs, 26px in a header row next to the gear. At most one main button per view. Destructive is only for confirming a removal. Bootstrap's btn-primary, btn-danger and btn-success go.

42 · beside inputs30 · toolbars, dialogs26 · header row
Main
accent text, 7% fill, 35% border
Secondary
surface, soft border
Quiet
text only, muted
Destructive
the main style in red
Disabled
A dialog footer

Icon buttons and the gear

Tools are 26px, muted at rest, default text and the hover fill on hover. The gear is the one control in the row that opens configuration, so it has a frame: at rest a soft border, on hover the strong border, open the main-button tint. Bootstrap gear-fill at 14px, one class .blockr-gear-btn, tooltip "Settings". Hover them.

rest hover open
Small icons, at rest and on hover: Wind

Handles and remove buttons are thin, muted, hidden until their row is hovered or focused; remove turns red on its own hover. Fold arrows stay visible.

7Floating layer

Menus hold choices and actions and open on click; tooltips name things on hover. Both use the raised surface with shadow-md. blockr has no popovers. rules: design-system#floating

Raised surface, 1px border, radius 8, 4px padding. Rows 32px at 14px with 10px side padding; meta 13px, muted, turning default on the hovered row. The current pick is weight 600. Disabled rows stay listed and say why in a tooltip. Group titles take the section-title style, sticky while the list scrolls; a divider separates groups without a title.

A field dropdown: filter box above 8 options, groups, hovered row, current pick, disabled row

None
Categorical
ARM3 levels
SEX2 levels
RACE6 levels
AGEGR13 levels
USUBJID254 levels
Numeric
AGEint
BMIBLdbl
WEIGHTBLdbl

An action menu: the block's details head it, a divider sets off the removal

subset blockblockr.core
Keeps the rows that match the conditions, and the chosen columns.
Rename
Copy block IDblock_7f3a
Remove

Placement

  • One placement routine for every menu (Select's computePosition, moved to blockr.ui).
  • A field dropdown takes the control's width, at least 190px. A menu from a button or a word sizes to its content, 180 to 320px.
  • It lines up with the trigger's left edge, or its right edge when the trigger is in the header row.
  • Up to 320px high, then the list scrolls under its sticky titles and filter box; 8px from the viewport edge.
  • A menu closes on a pick, on Escape and on a click outside it.

Open the live ones: a field, a word and the "…" of a header row.

Mean ozone by month

Mean by

Colour by
Aggregate

Tooltips

One style for every name shown on hover: a small light card on the raised surface, 12px, soft shadow. Every icon-only button and every cut-off label gets one; nothing else does, and no native title tooltips remain. Hover the icons and the cut-off names.

Drawn open over the gear and the cut-off label; hover the others.

Settings
Dictionary-Derived Term (AEDECOD)Dictionary-Derived Term (AEDECOD)

A chart's data tooltip uses the same surface: the swatch carries the series colour, labels muted, values at 500 with tabular figures.

July
Mean ozone59.1
Days26

No popovers: a block's package and description head its "…" menu, and "Copy block ID" is a menu item. The block subtitle is plain text.

Adding a block, and picking one on the board

Every "+" opens the same menu: search on top, category titles, one row per block type with its mark and package. Placing, linking or stacking a block that is already on the board uses the same menu, listing the board's blocks with their type as meta. Type to filter; arrows and Enter work.

8Labels and status

Shape tells the four small kinds apart: a badge (read only) has round ends; a tag (a value you picked), a pill (a click changes a setting) and a button have 4px corners. A badge has colour only for status or "on". rules: design-system#labels

Badges, tags, pills, counts

badgeCategoricalblockr.dplyrAddedNot foundStaleSaved
tagOzoneMonthWindMonth drawn hovered
pill
count
  • Badge: 18px capsule, 11px weight 500, 7px side padding, bg-subtle, border-default, muted. Types, packages and significance levels are neutral.
  • Accent tint on a badge means on, added or cutting rows. Status tints only for danger, warning and success; blue "info" becomes neutral.
  • Tag: 24px, 13px, text-default. Its × shows on the hovered tag only and turns red on its own hover.
  • Pill: 24px with a caret; it opens a menu. The click-to-cycle pill is retired.
  • Count: 16px capsule in the host's colour at 14%, text accent-700 (4.5:1 for small text).
  • One class each in blockr.ui: .blockr-badge, .blockr-tag, .blockr-pill, .blockr-count. The word "chip" goes.

Messages and the status line

Each status has a text, a background and a border colour; a message uses all three. The status line under an output is 12px muted text.

Error. Column "Ozone2" not found in the data.
Warning. 37 rows have missing values and were dropped.
Saved. The board was saved as "airquality exploration".
Note. New directory (created on save).
116 of 153 rows · 37 dropped (missing ozone)

Column names and their labels

The board speaks in names, an exhibit in labels. Where a column is picked it shows its name with the label as muted meta text, cut first. An exhibit shows the label only; hover an output table header for the name. Printed filter text uses labels; the board's pills and receipts use names.

Picking (a tag, a filter row)

AGEAgeTRTEMFLTreatment Emergent Analysis Flag
SAFFLSafety Population Flag=Y

An exhibit (hover a header)

Unique Subject IdentifierAgeSex
01-701-101563F
01-701-102364M

Filter text

Filtered: Sex = F; Age 18 to 64

On the board

SEX = FAGE 18 to 64

Showing that a block filters

The control that undoes a filter wears the accent tint while a filter is on, carries the number of active filters and names the clauses in its tooltip. It is the 26px button. Where the count is itself a control, count and reset join as one segment. Dots are for block status only.

Filters on (hover Reset)

No filter

Count and reset as one segment

9Special blocks

Charts, the crossfilter and the code editor follow the same anatomy and tokens as every block; their own settings are local tokens and the mark group. rules: design-system#special

Charts

The canvas reads the tokens at render: axis and data labels text-muted, axis line border-strong, split lines border-default, separators bg-surface, reference lines border-danger, face from the body. Canvas text is one size, --blockr-mark-font-size. A facet strip takes the section-title style. Exports (PNG, pptx) take the light values.

Days by temperature, per month

Count of days by band, facet by

May
Cool18 Mild11 Warm2
July
Cool0 Mild5 Warm26

Cool below 70°F, warm above 84°F.

62 of 153 rows

Crossfilter

The standard header row, with Reset above every filter it clears. "Group by" labels a field; "Filter by" titles a section. Inside a card: search 30px, 26px icons always shown, rows 30px, a 200px scroll area (about six rows). The bars are supporting marks.

Group by
Filter by
Month5 values
Search
May31
June30
July31
August31
September30
Wind4 bands
Search
Calm (under 5)3
Light (5 to 10)17
Moderate (10 to 15)10
Strong (15 and over)1
Day31 values
Search
31 of 153 rows

Code editor

The editor is a large input: field background, 13px mono. Its colours are four local tokens, --blockr-code-keyword, -string, -number and -comment, pointing at text-accent, text-success, text-warning and text-muted. Line numbers muted, the active line bg-hover, the input line a band mixed from the accent.

Script
1library(dplyr)
2# summer days with enough sun
3airquality |>
4 filter(Month %in% c(6, 7, 8), Solar.R > 150) |>
5 summarise(ozone = mean(Ozone, na.rm = TRUE), label = "summer")
1 row

10For package authors

Check a block against this list before release, in light and in dark. rules: design-system#authors

  • Read meaning tokens only. No hex, no palette step, no grey outside the ramp.
  • Name a package's own settings --blockr-<owner>-* and read them only in its own stylesheet.
  • Take the face from the body (--bs-body-font-family); do not name a font.
  • Put fields in the grid and give each a size: one column, two, or the full row.
  • Labels 12px muted, 4px above; section titles from the one class. No heading inside a block.
  • On/off is a checkbox; two or three fixed values a segmented control; more, or data, a Select; several, tags.
  • One gear per block, .blockr-gear-btn, only when there is something to configure. No Apply button.
  • Every icon-only button and cut-off label has a tooltip; no native title.
  • Buttons: the tinted main at most once per view; no btn-primary, btn-danger, btn-success.
  • Badges neutral unless they state a status or that something is on.
  • Menus through blockr.ui's surface and placement; no popovers.
  • Chart canvas text from --blockr-mark-font-size, ink from the tokens at render.

Appendix

Where the code lives and the legacy names. rules: design-system#appendix

Where the code lives

  • blockr.ui owns the tokens: inst/assets/css/blockr-tokens.css and blockr-tokens-dark.css.
  • blockr.ui owns the shared components: badge, tag, pill and count classes, the gear button, Blockr.Select and the one menu placement routine.
  • blockr.dock owns the dock header and the block category colours (blk_color()).
  • blockr.theme owns data colours (chart palettes, map markers).