Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sizing with CSS

Every layout property the CSS subset accepts, the units a length may be in, how a percentage resolves, and the tokens that move a control's size.

By the end of this chapter you can write a stylesheet that sizes a tree, know which declarations the engine drops, and switch a whole application to compact density with one argument.

The subset

The engine applies these layout properties and drops every other one. A dropped declaration is not an error. The engine logs it and carries on, and StyleLint reports every dead declaration in a sheet, which is how the toolkit’s own sheets are held to zero.

PropertyValues
flex-directionrow, row-reverse, column, column-reverse
justify-contentflex-start, center, flex-end, space-between, space-around, space-evenly
align-items, align-selfauto, flex-start, center, flex-end, stretch, baseline
align-contentThe same words, and space-between, space-around, space-evenly for wrapped lines
flex-wrapnowrap, wrap, wrap-reverse
width, heightA length, a percentage, or auto
min-width, max-width, min-height, max-heightA length or a percentage
padding, padding-top, padding-right, padding-bottom, padding-leftOne to four lengths, no auto
margin, margin-top, margin-right, margin-bottom, margin-leftOne to four lengths, auto, negatives allowed
gapOne length, for both axes
flex-grow, flex-shrinkA number of zero or more
flex-basisA length, a percentage, or auto
positionstatic, relative, absolute
inset, top, right, bottom, leftOne to four lengths
overflowvisible, hidden, scroll, auto

start and end are accepted for flex-start and flex-end wherever an alignment keyword is. left and right are not, because they are not the same under right-to-left text. A shorthand with one bad part is dropped whole, so padding: 8px nonsense applies to no edge rather than two.

Not in the subset: display, box-sizing, aspect-ratio, row-gap and column-gap, calc(), grid, and position: sticky. A sticky header is an affix. A value the engine cannot express is dropped rather than approximated, and the reason is in ADR-0005.

Units

UnitMeaning
pxA logical pixel. The display scale is applied once, below the stylesheet
%A percentage of the parent’s matching dimension
emThe element’s own computed font size
remThe root element’s computed font size
0Unitless zero is accepted. Any other unitless number is dropped
autoWhere the property takes it: width, height, margin, flex-basis and align-self

em and rem are resolved when the declaration is read, because Yoga has no concept of a font size. em is the element’s own computed size and rem is the root element’s. vh, vw, pt and cm are not units here.

A percentage width is of the parent’s width, a percentage height of its height, and a percentage flex-basis of the parent’s main axis.

Direction and gap

column id="form" {
    row class="field-row" { text "Name"; text-input }
    row class="field-row" { text "Email"; text-input }
}
#form { gap: 12px; padding: 16px; }
.field-row { gap: 8px; align-items: center; }

gap is one length and applies between rows and between columns alike. A row and a column ignore flex-direction. On any other box, a panel or a card, the stylesheet chooses it.

Padding and margin

panel id="dialog" {
    text "Delete this file?"
    row class="actions" {
        spacer
        button press="dismiss" "Cancel"
        button class="danger" press="delete" "Delete"
    }
}
#dialog { padding: 16px 24px; margin: 0 auto; }
.actions { gap: 8px; margin-top: 16px; }

The shorthand is CSS’s: one value is every edge, two are vertical and horizontal, three add a bottom, and four go clockwise from the top. margin: 0 auto centres a box on the main axis of a container it does not control, which is the reason margin was wanted. align-self: center centres on the cross axis. Negative margins pull a box over its neighbour and are not clamped.

Alignment

row id="status" {
    text "Listening on 8080"
    spacer
    badge class="success" "Live"
}
#status { align-items: center; justify-content: space-between; }
#status badge { align-self: flex-start; }

justify-content places children along the main axis and align-items across it. align-self is one child’s answer to align-items, and align-self: auto is a real declaration that undoes a more general rule.

Wrapping

row id="tags" {
    chip "Unread"
    chip "Starred"
    chip "Archived"
    chip "Shire"
    chip "Rivendell"
}
#tags { flex-wrap: wrap; gap: 8px; align-content: flex-start; }

flex-wrap: wrap lets a row break into lines. align-content places the lines across the container. Wrapped lines share a cross axis, which is ADR-0374.

Growing, shrinking and the basis

row id="editor" {
    column id="files" { text "main.java"; text "Window.java" }
    column id="source" { text "// the file" }
}
#files { flex-basis: 220px; flex-shrink: 0; }
#source { flex-grow: 1; flex-basis: 0; }

flex-grow shares out the space left after content. flex-shrink is 1 by default, which is CSS’s rule and the reason every control in controls.css declares flex-shrink: 0. flex-basis: 0 with flex-grow: 1 is a fixed share of the row, counted after the gaps, which is how a masonry’s columns are equal without anybody counting them. flex-basis: auto asks width or the content.

Width, height and limits

card id="note" {
    text "A card no wider than a readable line and no narrower than its title."
}
#note { width: 50%; min-width: 320px; max-width: 640px; }

width and height are preferred sizes. A cramped row shrinks a box below its width unless flex-shrink is 0 or min-width holds it. The four limits arrived in ADR-0181. “No limit” is undefined rather than zero, because a maximum of zero is a box that may not exist.

Position, inset and overflow

stack id="portrait" {
    panel class="picture" { text "GB" }
    badge "3"
}
#portrait badge { position: absolute; top: 4px; right: 4px; }
.picture { overflow: hidden; }

An absolute child is placed against the nearest ancestor that is not static, from its border box. inset takes the same one-to-four shorthand as padding. overflow: hidden clips. scroll and auto size the same as hidden and differ only in whether a scroll viewport offers bars. A stack sets position: absolute on its later children itself.

Density and the size tokens

A control’s height is the one metric that is a token rather than a literal, because the design system says a user preference moves it. Padding, gap and radius stay literal. The compact density is three custom properties and no rules, in the same cascade layer as a theme, and regular ships no stylesheet at all because it is what controls.css already is.

var sheets = Controls.stylesheets(Theme.NORD_DARK, Density.COMPACT);
TokenRegularCompactRead by
--gb-control-height32px28pxEvery control’s height, through --gb-button-height, --gb-input-height and the rest
--gb-list-row-height32px26pxlist, tree, a select’s rows
--gb-table-header-height36px30pxtable
--gb-code-box-width, --gb-code-box-height40px, 48px36px, 44pxcode-input
--gb-calendar-day32px28pxThe date picker’s day cell

The other size tokens a layout reads do not move with density.

TokenValueRead by
--gb-field-label-width120pxA field’s label column
--gb-scroll-line20pxA scroll, as one wheel line and one arrow key
--gb-scrollbar-gutter0px, 12px with Scrollbars.ALWAYSThe room a scroll reserves beside its content
--gb-scrollbar-size10px, 12px with Scrollbars.ALWAYSThe bar’s thickness

A widget of your own that reads var(--gb-control-height) is compact-aware with no code. One that writes 32px cannot be made so by anything in the density sheet.

An application’s own sheet goes after the toolkit’s, in the application layer, and is written the same way.

var sheets = new ArrayList<>(Controls.stylesheets(Theme.NORD_DARK, Density.REGULAR));
sheets.add(Stylesheet.resource(CascadeLayer.APPLICATION, App.class, "app.css"));

How the layers cascade and what else a sheet may say is in Styling.

Read more