This is the reference for how blockr looks and behaves: tokens, the
block, its layout, controls, actions, the floating layer, labels and the
special blocks. It states each rule once, with exact values and token
names. The design system page
draws the whole system with the same table of contents, so every section
here has an anchor of the same name there. Token values come from
inst/assets/css/blockr-tokens.css and
blockr-tokens-dark.css in blockr.ui.
1. Principles
- Components read meaning tokens only. Palette tokens
(
--blockr-grey-500,--blockr-blue-600) are read by other tokens and never by a component. - No UI colour outside the palette, and no grey that is not a ramp step. Data colours and block category colours are the two exceptions, each with its own source (see Foundations).
- Tints are computed from meaning tokens with
color-mix(), never from a palette token and never written as rgba literals, so they follow a theme and the dark scheme. The ones used in many places are tokens themselves (see Borders). - Three text levels: default, muted, disabled.
- Every configurable option is in the gear. A block with no options has no gear. Simplified mode removes the gear button and nothing else.
- Changes apply as they are made. There is no Apply button in a block’s settings. Text and numbers commit on Enter or blur.
- One control per kind of decision (see the table in Controls). Shape tells a reader what can be clicked: badges have round ends, things you act on have 4px corners.
- Breakpoints follow the panel, never the viewport. Every size step is a container query on the block.
- Floating things are menus (choices and actions, opened by a click) and tooltips (names, on hover). There are no popovers.
- The sentence on a block’s face looks as it will in print.
Drawn in the design system page.
2. Foundations
Token grammar
The second word of a token’s name says what kind it is.
| Kind | Pattern | Example | Read by |
|---|---|---|---|
| Palette | --blockr-<hue>-<step> |
--blockr-grey-200 |
other tokens only |
| Meaning | --blockr-color-<property>-<role>[-<state>] |
--blockr-color-text-muted |
components |
--blockr-font-<aspect>-<step> |
--blockr-font-size-sm |
||
--blockr-radius-<step>,
--blockr-control-h[-<step>],
--blockr-shadow-<step>
|
--blockr-radius-lg |
||
--blockr-focus-<aspect>,
--blockr-transition,
--blockr-mark-<aspect>
|
--blockr-focus-outline |
||
| Local | --blockr-<owner>-<thing> |
--blockr-dock-spinner-delay |
its owner’s stylesheet only |
A global token’s second word is one of color,
font, radius, control,
shadow, focus, transition,
mark, or a palette hue. Anything else is local. For colour,
<property> is text, bg or
border and comes first, because a colour chosen for text is
not automatically right as a fill.
Palette
| Hue | Steps | Light values |
|---|---|---|
| grey | 50 100 200 300 400 500 600 700 800 900 |
#f9fafb #f3f4f6 #e5e7eb
#d1d5db #9ca3af #6b7280
#4b5563 #374151 #1f2937
#111827
|
| blue | 50 100 500 600 700 |
#eff6ff #dbeafe #3b82f6
#2563eb #1d4ed8
|
| red | 50 100 600 700 |
#fef2f2 #fee2e2 #dc2626
#b91c1c
|
| amber | 50 100 500 700 |
#fffbeb #fef3c7 #f59e0b
#b45309
|
| green | 50 100 600 700 |
#f0fdf4 #dcfce7 #16a34a
#15803d
|
| accent | 50 100 500 600 700 | points at blue |
The meaning tokens use grey 50, 100, 200, 300, 500 and 900, plus 400 for disabled.
Text
| Token | Light | Use |
|---|---|---|
--blockr-color-text-default |
grey-900 | body text, values, headings (section headers, table headers, chart titles), menu items, tooltip body, code, the hover state of a muted icon |
--blockr-color-text-muted |
grey-500 | labels, placeholders, meta, hints, counts, empty states, column
types, row numbers, NA, status lines and footers, icons at
rest |
--blockr-color-text-disabled |
grey-400 | disabled controls only |
--blockr-color-text-on-accent |
#ffffff |
text on a solid accent fill |
--blockr-color-text-accent |
accent-600 | accent text |
--blockr-color-text-accent-strong |
accent-700 | accent text on an accent tint (a count on the main button, the avatar initial), where accent-600 is 3.9:1 |
--blockr-color-text-danger / -warning /
-success
|
red-700 / amber-700 / green-700 | status text |
Muted is 4.8:1 on white, 4.6:1 on grey-50 and 4.4:1 on grey-100.
Muted text therefore never sits on a hover or selected background: on a
hovered row, meta text and icons turn default; on a selected row, they
turn text-accent with the rest of the row. Grey-400 is
2.5:1 and serves disabled controls only.
Surfaces
Surfaces are layers. Each sits on the one before, and anything placed on a layer takes that layer’s field and border tokens.
| Token | Light | Job |
|---|---|---|
--blockr-color-bg-page |
grey-50 | the board canvas |
--blockr-color-bg-surface |
#ffffff |
a panel, a card, a block |
--blockr-color-bg-raised |
#ffffff |
menus, dropdowns, tooltips |
--blockr-color-bg-subtle |
grey-50 | a quiet fill inside a surface; the gear tray |
--blockr-color-bg-field |
grey-50 | an input field |
--blockr-color-bg-hover |
color-mix(in srgb, var(--blockr-color-text-default) 6%, transparent) |
hover and the keyboard row |
--blockr-color-bg-selected |
bg-accent-subtle |
the chosen thing: a selected row, the front tab of the active dock
group; its text is text-accent, and it has no edge |
--blockr-color-bg-selected-inactive |
color-mix(in srgb, var(--blockr-color-text-default) 10%, transparent) |
the front tab of every other dock group: selected, but not where you work; one step above hover |
--blockr-color-bg-accent |
accent-600 | solid accent fill (checked checkbox) |
--blockr-color-bg-accent-hover |
accent-700 | its hover |
--blockr-color-bg-accent-subtle |
color-mix(in srgb, var(--blockr-color-border-accent) 7%, transparent) |
the accent tint (below) |
--blockr-color-bg-accent-subtle-hover |
the same at 13% | its hover |
--blockr-color-bg-danger / -warning /
-success
|
red-50 / amber-50 / green-50 | status fills |
bg-hover is a wash of the text colour, so a hovered row
is one step away from whatever it sits on, surface or raised, in both
schemes. In light it lands on grey-100.
Borders
| Token | Light | Use |
|---|---|---|
--blockr-color-border-default |
grey-200 | every border, input borders included |
--blockr-color-border-strong |
grey-300 | hovered borders, stronger lines, a border drawn with a text colour |
--blockr-color-border-accent |
accent-600 | focus, full-strength accent edge |
--blockr-color-border-danger / -warning /
-success
|
red-600 / amber-500 / green-600 | status edges |
--blockr-color-border-accent-subtle |
color-mix(in srgb, var(--blockr-color-border-accent) 35%, transparent) |
the accent tint’s edge |
--blockr-color-border-danger-subtle /
-success-subtle
|
the status border at 35% | the edge of a status badge, a message, the destructive button |
--blockr-color-border-warning-subtle |
the warning border at 45% | the same; amber is paler, so its edge is stronger |
Input borders stay soft (border-default). The focus
state marks the active field.
The accent tint is text-accent on
bg-accent-subtle with a border-accent-subtle
edge; its hover is bg-accent-subtle-hover with
border-accent. It is the main button’s look, and every
selected or “on” state borrows it: a selected segment, an icon tile, a
pressed icon button, the open gear, a tag that is cutting rows, an “on”
badge, the “+N” overflow, the Enter button, a selected row and the
active group’s front tab (these two without the edge). Hovering a
selected thing keeps its look.
Status
Every status role has a text, a background and a border token:
danger, warning, success. error is danger;
there is one red. Warning’s border is amber-500, #f59e0b.
Blue is never a status colour, because blue means “on”. Negative numbers
in a table are data, and their colour belongs to blockr.theme.
Focus
Two styles:
| Element | Focus | Shown on |
|---|---|---|
| a field: text, number, select, code editor, a name being renamed |
border-accent and --blockr-focus-ring
(0 0 0 3px rgba(37, 99, 235, 0.12)) |
:focus, mouse included, because it also means “you are
typing here” |
| everything else: buttons, tools, the gear, segments, checkboxes, tabs, pills, legend items |
outline: var(--blockr-focus-outline) (2px solid
border-accent),
outline-offset: var(--blockr-focus-offset) (2px) |
:focus-visible only, so a mouse click shows
nothing |
The ring alone is 1.2:1 on white and does not show on a frameless
tool; the outline is 5.2:1. The 2px offset keeps it off the gear’s frame
and a selected segment’s tint. The keyboard row in a menu is not focus
(focus stays in the filter box) and keeps the hover look. The ring is
written as a literal, so a theme that changes the accent restates it.
The 0.45 ring in blockr.viz and blockr.ggplot
(--blockr-color-focus) goes.
Dark scheme
The dark scheme is blockr-tokens-dark.css, keyed off
data-bs-theme="dark" (the attribute bslib writes and
blockr.core’s dark-mode option toggles). It restates the grey and blue
ramps positionally (grey-50 is still the quietest surface, grey-900 the
loudest text) and restates the accent and status meaning tokens
directly.
| Token | Dark |
|---|---|
bg-page / bg-surface /
bg-raised
|
#0e1219 / #161b23 /
#1e242e
|
bg-hover |
the same 6% text wash, lighter than what it sits on |
bg-selected, bg-selected-inactive
|
follow the accent tint and the text colour; not restated |
text-on-accent |
#0e1219 (the accent fill is lighter in dark) |
text-accent |
accent-500 |
text-accent-strong |
text-accent |
text- / border-danger,
-warning, -success
|
#f87171, #fbbf24,
#4ade80
|
bg-danger, -warning,
-success
|
the same hues at 12% |
focus-ring |
0 0 0 3px rgb(96 165 250 / 0.28) |
| shadows | the same geometry at 0.5 to 0.7 black |
The border steps (grey 100 to 300) are compressed harder than a
mirror, because a light line on a dark field reads stronger than a grey
line on white. In dark the gear tray takes bg-page. Exports
(PNG, pptx) always take the light values.
Theming
A theme overrides the palette, most usefully the accent ramp
(--blockr-accent-50, -100, -500,
-600, -700); every accent-derived token
follows, and blue-600 keeps meaning blue. A theme is
typically those five values, optionally the ten greys (for warm or cool
neutrals), and perhaps a radius and a font face. A theme that changes
the accent also restates --blockr-focus-ring. Colours
painted in JS or R (charts, plots) come from blockr.theme’s palettes,
not from these tokens.
Type
The body face is Open Sans, as bslib’s Shiny preset ships it, set in
one place: --bs-body-font-family. A theme that ships a face
sets --bs-body-font-family. There is no
--blockr-font-sans. The chart canvas takes its face from
the body.
| Token | Size | Use |
|---|---|---|
--blockr-font-size-xs |
12px (0.75rem) | field labels, section titles, captions, status lines, tooltips, offers |
--blockr-font-size-sm |
13px (0.8125rem) | output subtitle and sentence, code, menu meta text, tags |
--blockr-font-size-base |
14px (0.875rem) | body, field values, menu rows, buttons at 42px, the block title |
--blockr-font-size-lg |
16px (1rem) | output title, the one heading on an output block |
--blockr-font-size-xl |
20px (1.25rem) | page titles outside the board (the block title is 14px,
base) |
There is no md step. Two sizes sit off the scale on
purpose: badges and counts at 11px, and text on a chart canvas at
--blockr-mark-font-size (11px).
| Weight token | Value | Use |
|---|---|---|
--blockr-font-weight-normal |
400 | body, labels, values |
--blockr-font-weight-medium |
500 | buttons, badges, pills, slot words, a selected segment, values in a chart tooltip |
--blockr-font-weight-semibold |
600 | block title, output title, section titles, counts, the current pick in a menu |
--blockr-font-mono is
'SF Mono', 'Fira Code', 'Consolas', 'Monaco', monospace.
Code is 13px on every code surface.
Shape and size
| Token | Value | Use |
|---|---|---|
--blockr-radius-sm |
4px | 26px buttons and tools, the gear, tags, pills, checkbox, menu rows, offers |
--blockr-radius-md |
6px | 30px buttons, segments inside a segmented control, messages |
--blockr-radius-lg |
8px | fields, 42px buttons, rows, menus, the gear tray, tooltips (the light card and the chart’s) |
--blockr-radius-xl |
12px | block panels |
--blockr-radius-pill |
999px | badges, counts |
| Token | Value | Use |
|---|---|---|
--blockr-control-h |
42px | inputs, selects, segmented controls in the grid, rows, buttons beside inputs |
--blockr-control-h-sm |
30px | buttons in toolbars and dialogs, the crossfilter card search |
--blockr-control-h-xs |
26px | header tools, the gear, buttons in a header strip, the builder/code switch, segmented controls inside a row |
No control is smaller than 26px, except what sits inside a 42px row or field (the 24px pill, tag and Enter button) and the 16px checkbox box, whose label is part of its target.
Elevation
| Token | Value |
|---|---|
--blockr-shadow-sm |
0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1) |
--blockr-shadow-md |
0 4px 12px rgba(0, 0, 0, 0.1) |
--blockr-shadow-lg |
0 25px 50px -12px rgb(0 0 0 / 0.25) |
Menus, dropdowns, tooltips and the chart tooltip use
shadow-md on bg-raised. The gear tray is in
flow and has no shadow. In dark, shadows barely read; the lighter
bg-raised separates a menu from the surface.
Motion
--blockr-transition: 0.15s ease for hover and state
changes. The gear tray slides open and closed over 0.22s, so the output
is seen moving down and up.
Data marks
| Token | Value | Rule |
|---|---|---|
--blockr-mark-radius |
2px | on the silhouette of a bar or box, on the value end only; ends on an axis or against a sibling stay square; never near half the mark’s thickness |
--blockr-mark-font-size |
11px | all text drawn on a chart canvas: axis labels and names, data labels, legend keys |
--blockr-mark-weight-supporting |
0.6 | opacity of a mark that supports a choice (crossfilter picker bars) |
Data colours (chart palettes, map markers) come from blockr.theme, never from UI tokens.
Block category colours
Okabe-Ito, one per category, through blk_color() in
blockr.dock. A fixed set: identical under every theme and in dark
mode.
| Category | Colour |
|---|---|
| input | #0072B2 |
| transform | #009E73 |
| structured | #56B4E9 |
| plot | #E69F00 |
| table | #CC79A7 |
| model | #F0E442 |
| output | #D55E00 |
| utility | #CCCCCC |
| uncategorised | #999999 |
Drawn in the design system page.
3. The block
Anatomy, top to bottom
-
Dock header (compact). One row: the block’s mark (a
28px tinted square in its category colour, carrying the status dot), the
block title at 14px (
font-size-base) weight 600text-default, the actions (controls, preview, “…”) on the right. No subtitle: the block type and package are the mark’s tooltip (“filter block · blockr.dplyr”). About 52px. - Header row. Output blocks put the output title and the sentence on the left and the tools on the right, gear last. Transform blocks have the tools only.
- Gear tray. Opens in flow directly under the header row.
- Controls on the face. Transform blocks only: fields in the grid, or rows for lists you add to and reorder.
- The output. Body, caption, status line.
There is no 16px section heading inside a block.
The block’s mark and bare mode
- The mark is a tinted square (the category colour at about 18% on the surface) with the glyph in the category colour. Sizes: 28px in the dock header, 24px in list rows (menus, pickers) with the package as a badge, 16px in a tab.
- The header’s mark carries the block type and package as its tooltip; it is the one tooltip on something that is not a button or a cut-off label.
- Bare mode (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, and a double-click on the tab renames. It needs dockViewR’s tab components (cynkra/dockViewR#102); there is no CSS-only interim.
Header row
- Tools are 26px, right-aligned, gear last.
- The output title shares the row with the tools. It takes the width the tools leave and wraps down.
- The output title is 16px (
font-size-lg), weight 600,text-default. The subtitle below it is 13pxtext-muted. Title and subtitle belong to the output: they export with the chart or table.
What stays on the face
- A transform block’s face holds the fields that define it; the gear holds the rest.
- An output block’s face holds the output and its sentence. A slot in the sentence is the only way to put one of its arguments on the face. Its gear keeps a row for every argument, slotted or not.
- Reader controls (for example the patient profile’s view toggles) sit on the face, not in the gear.
- Simplified and locked mode hide the gear button and nothing else.
Dock does this with one CSS rule on
.blockr-gear-btn; blocks never ask which mode they are in.
The gear tray
- In flow under the header row, above the face’s controls. It pushes the content down and covers nothing. It slides open over 0.22s.
- Look:
bg-subtlefill, no border, radius 8, padding 12px 14px 16px, 16px below it. A 10px beak, 8px from the right edge, points at the gear. Fields inside turnbg-surface(white), so the tray reads as a separate layer. In dark the tray and its beak takebg-page. - No heading. The tray’s accessible name is an
aria-label. Sections are separated by a section title and 24px of space, with no rules. A tray with one section has no title at all. - A section switched on by a checkbox keeps the checkbox before its title; its fields appear below when checked.
- Fields use the grid (Layout).
- It closes by the gear and by Escape only. A click on the output, on another block or on a menu inside the tray leaves it open.
- The open state lasts for the session (a re-render does not close it) and is not saved with the board. Several trays may be open at once.
- Changes apply as they are made. Text fields commit on Enter or blur. No tray has an Apply button for its fields; code is the one exception and commits on its own button (Run in the code and function blocks, Apply on the chart’s prepare script).
- The crossfilter’s settings and the patient profile’s gear panel use the same tray.
The output
- Body: the chart, table or text.
-
Caption: 12px
text-muted. Chart, table and composer captions stay italic. -
Status line: 12px
text-muted: row counts, filter state, timing, the drill receipt. See Labels and status.
The sentence and its slots
The sentence is the output’s subtitle, written by the block’s author
in the #| subtitle: template. Nothing is generated; a
setting the template does not name stays in the gear.
-
Line breaks: one sentence. The author breaks lines
by writing
\nin the template; blockr adds no breaks of its own. -
A slot is a live word: accent text
(
text-accent), weight 500, a 1px dashed underline 3px below the text, solid on hover. A click opens the list under the word (Blockr.Select.menu()). A flag word toggles in place. -
Many values: a slot shows the first three values,
then “+N more”, inside the same live word; the click opens the full
list. Exports print the same text. The cut is 3
(
TITLE_MAX_VALUES). -
Offers (“+ name”, for a setting that is unset) sit
on their own line under the sentence, three at most. Past three, the
last one reads “More settings” and opens the gear. An offer is a small
dashed button: 12px
text-muted, 1px dashedborder-strong, radius 4, 6px side padding; on hover the border and text turn accent. It disappears once its setting is set. Offers are not part of the printed sentence. - A clause in
[ ... ]leaves with its value. The sentence never prints “(none)”. - One template, two renderers: the face draws live words, exports print the same text.
Drawn in the design system page.
4. Layout
The grid
One grid for fields, on the block’s face and in the gear tray alike. The columns are about 130px wide; the panel’s width decides how many there are.
| Panel width | Columns |
|---|---|
| under 290px | 1 |
| 290px and up | 2 |
| 560px and up | 4 |
| 840px and up | 6 |
| 1110px and up | 8 |
Gaps: 12px between columns, 16px between rows of fields. The grid steps on a container query on the block. It moves from blockr.extra to blockr.ui.
Field sizes
A field says how many columns it needs.
| Size | Columns | Fields |
|---|---|---|
| small | 1 | number, checkbox |
| large | 2 | select, text field, segmented control |
| full | the whole row | tags (multi-select), expression |
Columns line up across sections at every width. A section with few fields leaves room on the right; fields do not stretch to fill it. At two columns a large field fills the row.
Spacing
| Between | Space |
|---|---|
| label and its control | 4px |
| columns / rows of fields | 12px / 16px |
| a section title and its first field | 10px |
| the end of one section and the next section title | 24px |
| rows in a list | 6px |
| the gear tray and what follows | 16px |
Section titles
“A group of fields starts here”, on the face and in the gear tray:
12px (font-size-xs), weight 600, uppercase, 0.05em
tracking, text-muted. It works as a divider and must not
pull the eye from the fields. One class replaces the settings band
title, .dd-section-title, .dd-at-head,
.dd-type-group-head, the crossfilter table and search
headers and the select menu group titles.
Field labels
12px, weight 400, text-muted, 4px above the control, in
every package. The required-empty cue adds a trailing * to
the label.
Rows
A row in an add-and-reorder list (filter conditions, columns) is
42px, the same as an input, border included
(box-sizing: border-box), as .blockr-row is.
Look: bg-field, 1px border-default, radius 8,
padding 5px 10px, 10px gap between its parts, 6px between rows. Its
operator pill is 24px.
- A row’s content starts on the same edge as the label above it.
- The drag handle takes no space at rest. It appears in the container’s left padding, which is at least 14px.
- Handles and remove buttons appear on hover or keyboard focus of their row (see Actions).
- “Add condition” under the list is a quiet button.
Drawn in the design system page.
5. Controls
Which control for which decision
| Decision | Control |
|---|---|
| Turn an option on or off | Checkbox |
| One of two or three fixed, short values | Segmented control |
| One of four or more, or of anything from the data (columns, measures, levels) at any count | Select |
| The same, inside a row | Pill that opens a menu |
| Several from a list, from the data or from a fixed set | Select, multi, with tags |
| Kind of output (chart type) | Icon tiles, with group headings where types are grouped |
| How a value is edited (builder or code) | Pressed icon button |
| A number or a name | Field that commits on Enter or blur |
Not used: switches, radio buttons, click-to-cycle pills, toggle-pill groups, rows of checkboxes for a fixed set, shinyWidgets and unstyled Bootstrap inputs. The selected state of every choice control is the accent tint: a selected segment, an icon tile, a pressed icon button, the open gear, a selected row, the active group’s front tab.
Text and number fields
- 42px,
bg-field, 1pxborder-default, radius 8 (radius-lg), 12px side padding, 14pxtext-default. Placeholdertext-muted. - Focus:
border-accentand--blockr-focus-ring, on any focus. - Disabled:
text-disabled. - Required and empty:
border-warning(amber-500,#f59e0b),bg-warningfill, and a*after the label. The cue clears when the field has a value. - Commit on Enter or blur. The “Enter ↵” button (24px, 11px weight 500, the accent tint) is armed while the field is dirty. Escape reverts.
- Numbers use the same field. The input keeps
type=numberfor the keyboard, hides the spinner arrows, and commits like text; arrow keys still step. A Bootstrap override brings Shiny’snumericInputto the same look. - dplyr’s 30px inputs move to the 42px field.
Select
Blockr.Select is the only dropdown, single and
multi.
- In the grid it is a bordered 42px field with a caret. Inside a row it is bare.
- Options show the value first and the label after it as 13px muted meta text (for columns, see Column names and their labels); the label is cut first when space runs out.
- Its dropdown is a menu (Floating layer).
Select, multi, with tags
- The picks sit as tags in the control. The dropdown lists only what is not picked. Tags can be dragged to reorder.
- A tag’s × appears on the tag under the pointer or with keyboard
focus, never on every tag at once, and turns
text-dangeron its own hover. Its space stays reserved, so nothing moves. - The “+N” overflow stands in for hidden tags: tag geometry in the accent tint; a click expands the field.
- The same control serves data-driven lists and fixed sets (such as the summary table’s statistics). Fixed options stay hidden until opened.
Checkbox
On/off is a bare checkbox everywhere: in the gear tray, on a block’s
face and in the board options. Core’s bslib::input_switch
calls, Shiny’s checkboxInputs, the pharma header switch and
the on/off pills all become Blockr.checkbox; a Bootstrap
.form-check override covers the R side.
- Box: 16px, radius 4, 1px
border-strong,bg-surface. Hover: bordertext-muted. - Checked:
bg-accentfill and border, the check intext-on-accent. - Label after the box, 9px gap, 14px
text-default. The label names the “on” state. - A native input underneath; Space toggles; keyboard focus puts the focus outline around the box.
- Disabled: box at 50% opacity, label
text-disabled. - In the grid: the box and its words, with no field shell and no empty label row. It sits on the bottom line of its grid row (the controls’ line) and takes one column. On a row of its own it takes only its own height.
- Checkboxes that together encode one choice (core merge’s
all.x/all.y) become that choice as a Select (“Join type”).
If a boolean ever has to sit inside a field shell again, the switch question reopens.
Segmented control
For a fixed set of two or three short values. Both values stay in view and the pick is readable without a click.
- In the grid: 42px,
bg-field, 1pxborder-default, radius 8, 3px inner padding and gap. Segments share the width, radius 6, 14pxtext-muted; hovertext-defaultonbg-hover. - Selected segment: the accent tint, weight 500.
- It takes two columns, like a select. Three segments need about 240px.
- Inside a row: 26px, 2px padding and gap, radius 6, segments 12px at radius 4.
- Four or more values, labels too long for the cell, or values from the data go to Select.
- It replaces the cycle pills of this kind, viz’s Order select, the head block’s “Tail” switch (“From: First / Last”), stats’ shinyWidgets buttons and the dm, io and pharma segmented copies.
A pill that opens a menu
For four or more values inside a row: the filter operator, the join
key operator, viz’s .dd-func-btn (n, N, %, Σ).
- 24px inside a 42px row, radius 4, 1px
border-default,bg-surface, 12px weight 500text-muted, with a caret so it reads as a choice. Hover:border-strong,text-default. - A click opens
Blockr.Select.menu()anchored on the pill, listing every value. - The click-to-cycle pill is retired. Join type, outside a row, is a Select field in the grid.
Builder or code switch
A pressed icon button, 26px, the tool look (like the gear). Pressed:
the accent tint. It sets aria-pressed. Its tooltip names
the other state (“Edit as formula text”, “Back to the builder”). It
stays out of the field grid.
Renaming in place
One pattern for every name that can be edited where it is shown: the block title, an outline row, a view name, a tab (in bare mode).
- Start: a double-click on the name, or “Rename” in the item’s “…” or row menu. A single click keeps its other job (select, open).
- At rest the name is plain text; on hover it takes the
bg-hoverwash (radius 6, 4px 8px padding, pulled back by the padding so the text does not move). There is no pencil. - While editing, the text becomes a field at its own size and weight:
1px
border-accent, the focus ring,bg-surface, the text selected. No label. - Enter commits, Escape restores, a click elsewhere commits. An empty
or duplicate name is refused in place: the field’s
border-dangeredge and a one-line 12pxtext-dangermessage under it.
Drawn in the design system page.
6. Actions
Buttons
Four kinds. All are weight 500 and show the focus outline on
:focus-visible.
| Kind | Rest | Hover | Use |
|---|---|---|---|
| Main | the accent tint: text-accent,
bg-accent-subtle, border-accent-subtle
|
bg-accent-subtle-hover, border-accent
|
at most one per view |
| Secondary |
bg-surface, border-default,
text-default
|
bg-hover, border-strong
|
everything else with a frame |
| Quiet | text only, text-muted, 8px side padding |
bg-hover, text-default
|
low-weight actions; replaces the grey-400 text links |
| Destructive |
text-danger, border-danger at 6% fill,
border-danger-subtle
|
border-danger at 12% fill,
border-danger
|
only to confirm a removal |
| Disabled |
text-disabled, transparent,
border-default
|
none |
There is no solid accent button. Bootstrap’s
btn-primary, btn-danger and
btn-success go.
| Size | Height | Text | Side padding | Radius | Where |
|---|---|---|---|---|---|
| m | 42px | 14px | 16px | 8px | beside inputs |
| s | 30px | 13px | 11px | 6px | toolbars, dialogs |
| xs | 26px | 12px | 9px | 4px | a header strip, next to the gear |
The crossfilter reset, the patient-profile drill reset and dplyr’s Enter button are instances of the main style and take these numbers.
Icon buttons (tools)
Header tools (search, download, the builder/code switch) are 26px,
radius 4, bare, icon text-muted. Hover:
bg-hover, icon text-default. Every icon-only
button has a tooltip. Tools do not shrink with the panel.
The dock header’s tools (the controls and preview toggles, the “…”
menu) are quieter than other tools: at rest their icon is
border-strong grey, on hover text-disabled on
the bg-hover wash, so the header reads as the block’s name.
A toggle that is on shows its icon in text-accent, with no
fill and no frame. The accent tint’s fill and frame are for the
builder/code switch, which changes how a value is edited rather than
what is shown.
The gear button
- 26px, radius 4, 1px
border-default, the one framed square in the header row. Icon: Bootstrapgear-fillat 14px,text-muted. - Hover:
text-default,border-strong,bg-hover. - While the tray is open: the accent tint.
- One class,
.blockr-gear-btn, on every gear and on nothing else. Tooltip “Settings”. - No gear when a block has no options, including per mode.
Small icons
Drag handles, remove buttons and fold arrows are drawn with thin
strokes and small dots in text-muted. Handles and remove
buttons are hidden at rest and appear on hover or keyboard focus of
their row; remove turns text-danger on hover. Fold arrows
stay visible. The crossfilter’s filter cards are the exception (see Special blocks).
The navbar
The board’s toolbar: 48px high, bg-surface, a
border-default rule under it, every control 30px, tokens
only.
- Left: the logo, then the workflow’s name, then its save state as
12px
text-mutedtext (“Not saved”, “Saved just now”), then one quiet “…” workflow menu (save as, copy link, history, new). No split buttons. - The workflow’s name is its ID: chosen in a dialog at the first save, unique, like a file name (spaces allowed). It is shown in the body face, weight 600, never monospace. There is no separate title.
- Right: the view menu as a quiet button (current view marked by
weight 600, no fill), the board options gear, and the account avatar
last: a 28px circle with the count’s accent fill
(
border-accentat 14%) and atext-accent-stronginitial. - Hover on every navbar control is
bg-hover. The spinner’s label is the light tooltip.
Drawn in the design system page.
7. Floating layer
blockr has no popovers. Floating things are menus (choices and actions, opened by a click) and tooltips (names, on hover). The gear tray is in flow and not part of this layer.
Menus
Every menu, dropdown and list that floats uses one surface:
-
bg-raised, 1pxborder-default, radius 8 (radius-lg, the same as the field it opens from),shadow-md, 4px inner padding. - Rows 32px at 14px, radius 4 (
radius-sm), 10px side padding. Meta text 13px. - The dock “…” menu, the download menus, the add menu and the @ menu move to it. The beak belongs to the gear tray only.
Row states.
| State | Look |
|---|---|
| hover and keyboard row (same look) |
bg-hover; meta text and icons turn
text-default
|
| current pick | weight 600 |
| disabled | listed in text-disabled, no hover, the reason in a
tooltip |
The views menu drops its blue fill for the current view.
Group titles and dividers. A group title uses the
section-title style (12px, 600, uppercase, 0.05em,
text-muted), 8px above and 4px below, inset to the row text
(10px). In a list that scrolls it is sticky. A divider (1px
border-default, 4px above and below, edge to edge)
separates groups without a title, such as ordinary actions from a
destructive one. A destructive item is text-danger.
Placement. One routine for all of them (Select’s
computePosition, moved to blockr.ui): fixed position on
body, 4px from the trigger, flips above when there is no
room below, follows scroll, stays 8px from the viewport edge.
| Menu | Width |
|---|---|
| field dropdown | the control’s width, at least 190px |
| menu from a button or a word | sized to content, 180 to 320px |
A menu lines up with the trigger’s left edge, or its right edge when
the trigger is in the header row. Height up to 320px, then the list
scrolls under its sticky titles and filter box. A filter box (30px)
appears above 8 options. A Select.menu names the role it
sets in its title, because the word that opened it names only the
value.
Content. Long lists render up to 200 options and end
with a count row that asks you to type; server-searched lists say how
many values there are. Empty and loading rows are centred, italic,
text-muted (“No matches”, “All selected”, “Loading…”).
Behaviour. Arrows move, Enter picks, typing filters. Escape closes and returns focus to the trigger; Tab and an outside click close. A pick closes the menu; a multi pick keeps it open.
The block’s “…” menu
Its head holds the block’s name, package and description (12px
text-muted, the name in text-default weight
600), above a divider. Then the actions: Rename, Duplicate, Copy block
ID (the ID as mono meta text), and Remove after a divider in
text-danger. The dock header has no subtitle; the block
type is the tooltip on its mark.
Tooltips
One style for every name shown on hover: a small light card.
bg-raised, 1px border-default,
shadow-md, radius 8 (radius-lg, as the chart’s
data tooltip), 12px text-default, padding 5px 9px. It shows
on hover and on keyboard focus, and leaves with the pointer or focus;
Escape hides it.
- Every icon-only button and every cut-off label gets one, and so does the block’s mark in the dock header (its type and package). Nothing else does: a button that says its name has no tooltip.
- No native
titletooltips remain. - It matches the chart’s data tooltip (see Special blocks), so blockr has one floating style.
- It shows after the pointer rests 800ms, above the element; it flips below only where there is no room above.
Picking a block
- Adding a block is one menu at the “+”, everywhere: the outline, a block’s append, the board’s Add block. Search on top, category titles, one row per block type with its 24px mark, its name and the package as a badge. Rows carry no description. The block’s ID is generated, and its title is renamed on the block after it is added. The dock’s block browser sidebar and its “configure before adding” step go.
- Picking a block already on the board (add a panel, link to, add to a stack) uses the same menu, listing the board’s blocks: mark, title, and the block type as meta text. The Bootstrap modal with selectize goes.
Drawn in the design system page.
8. Labels and status
Four kinds, told apart by shape
| Kind | Class | Job | Shape |
|---|---|---|---|
| Badge | .blockr-badge |
you only read it: “Categorical”, “blockr.dplyr”, “Added” | capsule |
| Tag | .blockr-tag |
a value you picked, with × when it can be removed | 4px corners |
| Pill | .blockr-pill |
a click changes a setting (the operator) | 4px corners |
| Count | .blockr-count |
a number on a button | capsule |
The word “chip” goes. Round ends mean read-only; tags, pills and buttons, which you act on, keep 4px corners.
Badges
- 18px capsule (
radius-pill), 11px weight 500, 7px side padding,bg-subtlefill, 1pxborder-default,text-muted. - Neutral unless it states a status or that something is on:
-
on, added, cutting rows: the accent tint
(
bg-accent-subtle,border-accent-subtle,text-accent); -
danger, warning, success:
bg-<status>,border-<status>-subtle,text-<status>.
-
on, added, cutting rows: the accent tint
(
- Types, packages and significance levels are neutral; the type icon beside a name already carries the type. io’s blue “info” badge becomes neutral.
- Data colours (the arm label in the cohort list, the tile’s colour-by pill) come from the board’s scale and are outside this rule.
- A count with a unit (“4 blocks”) is a badge.
Tags
24px, radius 4, 1px border-default,
bg-surface, 13px text-default, 8px side
padding. The × follows Controls. The accent tint
on a tag means the value is cutting rows. The crossfilter’s “Filter by”
pills are tags by this rule, and the read-only items in a drill receipt
are tags without ×.
Pills
Specified under “A pill that opens a menu” in Controls.
Counts
- 16px capsule, at least 16px wide, 4px side padding, 11px weight 600, tabular figures.
- Fill: the host control’s colour at 14%. On an accent host that is
color-mix(in srgb, var(--blockr-color-border-accent) 14%, transparent)withtext-accent-strongtext. - Shown only when the number is above zero.
- On a secondary or destructive button the count takes that button’s
colour:
text-defaulton abg-hoverfill, ortext-dangeronborder-dangerat 14%. A count never brings a second colour into a button.
Showing that a block filters
- The control that undoes the filter (Reset) is a 26px xs main button while a filter is on, disabled when none is. It carries the number of active filters as a count, and its tooltip names the clause.
- Where the count is itself a control (the patient profile’s cohort tag opens the sidebar), count and reset join as one 26px segment.
- Dots are for block status only; the heatmap’s filter dot goes.
Block status
An 8px dot on the block’s mark with a 2px bg-surface
ring, one spec for dock and the DAG (block_status_style()).
Its fills are local tokens owned by blockr.dock, each pointing at a
meaning token:
| Token | Points at | State |
|---|---|---|
--blockr-dock-status-stale |
text-muted |
stale |
--blockr-dock-status-waiting |
border-warning, drawn as a hollow ring (1.5px,
bg-surface inside) |
an input is not ready: nothing linked in, or the block upstream is unset, waiting or failed |
--blockr-dock-status-failed |
border-danger |
failed |
--blockr-dock-status-unset |
border-warning |
a required input is empty; the same amber as the empty field’s cue |
Unset and waiting share the amber and differ by shape: the block that
needs you has the solid dot, and the blocks below it, which wait for it,
have rings. -unset replaces the off-palette
#eab308 in blockr.dock
block_status_style().
Status line and plain text
The status line under the output is 12px text-muted: row
counts, filter state, timing, the drill receipt. These stay plain text,
with no badge: column types in the table preview (int, dbl), the status
line and its receipt, the filter trail printed in captions (in column
labels, see below), the Big N at the end of a column header.
Column names and their labels
A column has a name (AGE) and often a label (“Age”). The
board speaks in names, an exhibit in labels.
| Where | Shows |
|---|---|
| a control that holds a column: Select options and the chosen value, tags, filter rows, crossfilter cards, gear fields, the @ menu | the name, then the label as meta text: 13px text-muted,
8px after the name, no separator, cut first when space runs out (12px
inside a tag) |
| an exhibit: chart axes, legends and tooltips, output and summary tables, exports | the label only; an output table header shows the name in its tooltip |
| the table preview (a building surface) | the name, with the label on its own line under it, 12px
text-muted
|
| filter text printed with an exhibit: the filter trail in captions and exports | labels: “Sex = F; Age 18 to 64” |
| filter text that stays on the board: crossfilter shelf pills, drill receipts | names: “SEX = F” |
- No label, or a label equal to the name: the name alone. Never “unset”, never the name twice.
- A tooltip that shows both reads
AGE · Age. - An aggregate reads “Mean of Age”, on a chart, in a table and in a rank table header.
- Code and decode pairs (PARAMCD and PARAM) follow the same rule: where one is picked, the code with the decode as meta (“ALB Albumin (g/L)”); in an exhibit, the decode, with the code in the tooltip.
- One reader for every package:
attr(x, "label", exact = TRUE), returning one string or nothing. Withoutexact, a column with haven value labels returns those, and the readers that expect one string break. - A sentence slot prints the name (
{@x}) or the label ({label(@x)}), as its author writes it.
Messages
- Required and empty: the amber cue on the field (Controls), never a banner.
- Syntax and validation text:
text-danger. - Empty and loading states in lists: centred, italic,
text-muted. - A block-level error or warning (a failed expression, dropped rows)
uses the message style, above the output:
text-<status>onbg-<status>with aborder-<status>-subtleedge, 13px, radius 6. Danger for errors, the amber set for warnings.
Drawn in the design system page.
9. Special blocks
Charts
Around the plot: the header row, output title, sentence, caption and status line follow The block. Axis titles, legend titles and tooltips use column labels (Labels); blockr.ggplot’s axis titles move from names to labels. Header tools are 26px (the chart’s and composer’s 30px tools move to 26px).
On the canvas: all text at
--blockr-mark-font-size(11px), read by chart.js once per render. The gutter measurement (11px), the 14px label-row budget and the printed charts keep their numbers.-
Canvas ink is read from the tokens at render, so it follows the scheme and a theme’s greys:
Canvas element Token axis and data labels text-mutedaxis line border-strongsplit lines border-defaultseparators, halo bg-surfacereference lines border-dangerface the body face Exports (PNG, pptx) take the light values. Retiring the “ECharts Theme” board option is a follow-up.
Facet strip: the section-title style (12px, 600, uppercase, 0.05em,
text-muted).Legend band: HTML. Its items take the focus outline. The 25 x 14px swatch stays.
Data tooltip: the raised card (
bg-raised,border-default, radius 8,shadow-md). Headline 13px weight 600 with the series swatch, which carries the series colour; rows 12px, labelstext-muted, valuestext-defaultat 500 with tabular figures.Slider bubble: stays inverse (dark label), because it labels a thumb during a drag.
Data colours: blockr.theme palettes.
Crossfilter
- Header row: the standard one. Row count, “Reset all” and the gear sit on the header row at the top. “Group by” is a field with a label; “Filter by” is a section title over the tags and cards. (The population filter, a block that joins two blocks in one, keeps its own layout.)
- Reset all: the 26px main button with a count (Labels).
- Filter by tags: 13px tags; a filtering tag wears the accent tint.
- Filter cards: search 30px; reset and remove icons 26px and always shown; rows 30px. The 200px scroll area shows about 6 rows.
-
Bars: accent fill at
--blockr-mark-weight-supporting, value end rounded by--blockr-mark-radius. - Settings: in the gear tray.
- Type badges in the column search are neutral badges (B4).
- Literal
#fff, rgba accents and Bootstrap reds in its stylesheet become tokens.
Code editor
The code, function and composer blocks share one CodeMirror editor.
The editor is a large input:
bg-fieldbackground, 13px mono.-
Syntax colours come from four local tokens:
Token Points at --blockr-code-keywordtext-accent--blockr-code-stringtext-success--blockr-code-numbertext-warning--blockr-code-commenttext-muted Line numbers
text-muted; the active linebg-hover; the input-line band is mixed fromborder-accent.Footer: Run and Accept all are 26px main buttons; Reject all is a 26px secondary button. The syntax message is
text-danger.Code is 13px on every code surface; dplyr’s expression input moves from 14px.
The green and red line backgrounds of an AI review diff are diff meaning and stay.
Composer
The composer keeps its busy cue (after 300ms the body dims to 0.45
and a clock runs under the sentence) and the “composed in” time that
fades after 2s, both in text-muted. Every block gets the
same cue: past 300ms of computing, its output dims to 0.45 and a clock
(“computing… 1.2 s”) runs under it; nothing shows for a faster run.
blockr.core applies it from outside the block.
Drawn in the design system page.
Appendix
Where the code lives
blockr.ui owns the tokens
(inst/assets/css/blockr-tokens.css,
blockr-tokens-dark.css) and the shared components: the
params grid (moving from blockr.extra), Blockr.Select and
its placement routine, Blockr.checkbox, the badge, tag,
pill and count classes, the slot and offer rules (today declared twice,
in blockr.viz chart.css and blockr.dm
crossfilter-block.css), the gear button, and the
column-label reader. blockr.dock owns the dock header, the “…” menu, the
block status dot, the category colours (blk_color()), the
status dot’s --blockr-dock-status-* tokens and the rule
that hides the gear in simplified mode. blockr.theme owns data
colours.
To do: blockr.ui’s token tests check that the vocabulary changes only deliberately and that consumers’ fallbacks agree with it. Two tests are still to write: one that enforces the grammar, and one that warns on every read of a legacy alias.
Legacy token aliases
Old names stay in blockr-tokens.css for a release and
each keeps its old value. Packages move over when convenient; the alias
goes when nothing reads it.
| Old name | Points at |
|---|---|
--blockr-color-text-primary |
color-text-default |
--blockr-color-text-secondary |
grey-700 (by job: labels and status lines go to
text-muted; headings, content and hover states to
text-default) |
--blockr-color-text-subtle |
grey-400 (by job: placeholders, meta and icons go to
text-muted; disabled controls to
text-disabled; a border to border-strong) |
--blockr-color-text-meta |
color-text-muted |
--blockr-color-border |
color-border-default |
--blockr-color-border-hover |
color-border-strong |
--blockr-color-bg-input |
color-bg-field |
--blockr-color-primary |
color-bg-accent |
--blockr-color-primary-hover |
color-bg-accent-hover |
--blockr-color-primary-bg |
accent-50 (keeps its old value; the accent tint
color-bg-accent-subtle is a 7% mix, nearly the same
colour) |
--blockr-color-error,
--blockr-color-danger
|
color-border-danger |
--blockr-color-success |
color-border-success |
--blockr-color-warning |
color-border-warning |
--blockr-color-warning-bg |
color-bg-warning |
--blockr-color-warning-text |
color-text-warning |
--blockr-font-size-section |
font-size-lg |
--blockr-font-size-title |
font-size-xl |
--blockr-shadow |
shadow-sm |
--blockr-shadow-dropdown |
shadow-md |
One new token reuses an old name:
--blockr-color-bg-hover was grey-100, and it stays grey-100
in light until the fallbacks that write #f3f4f6 move to the
text wash. The dark scheme uses the wash already.
Removed from the UI tokens: --blockr-color-negative
(data colour, belongs in blockr.theme). Local names move under their
owner: dock’s spinner-delay, sidebar-*,
stack-height and header settings to
--blockr-dock-*; viz’s rank-* to
--blockr-viz-rank-*. blockr.outline drops its own
--md-* variables and reads the blockr tokens.