Styling: the CSS cube and Tailwind

All of a block's styling goes through a single field, cssProperties. No CSS class hides among the settings any more: if you are looking for V20's vt_classNameElement_input, it became an entry in this field, under the input target.

This field was not merged into properties for a simple reason: it does not have the same shape. One styling property takes different values depending on the element being styled, the screen width and the element's state. Hence a four-level arrangement.

The shape: four levels, always

cssProperties[target][breakpoint][variant][property] = value
LevelValues
targetThe HTML element receiving the classes. bspk is the block itself and always exists; each type declares the others in its schema — input, label, button…
breakpointdefault, xs, sm, md, lg, xl, 2xl
variantdefault, hover, focus, active, disabled, dark, plus the business variants a type declares — selected for the listbox and the tree
propertyOne of the 79 declared styling properties

Aiming at a target the type does not declare is a structural error: the write is refused. That is deliberate — a mistyped target would otherwise produce styling that never shows up.

The unit trap

Eleven properties need a companion entry giving the unit. Without it, no class is generated at all: no error, no warning, the styling simply does not appear.

gap  width  minWidth  maxWidth  height  minHeight  maxHeight  top  bottom  left  right

Trap: width: 100 on its own does nothing. You need width: 100 and widthUnit: "px". This is the most frequent cause of "I set a width and nothing moved".

Everything else is implicit pixels and takes no unit: margins and padding, corner radii, border widths, text size.

Shadows are set as a whole

Six sub-properties — horizontal and vertical offset, blur, spread, colour, and the inset flag — are assembled together into a single class, then dropped individually. Setting one without the others gives you a shadow you did not ask for.

Tailwind: the compiled stylesheet is closed

output.css is pre-compiled. A class that was not extracted at build time does not exist at runtime, however correct it looks in the markup. That is the whole difference between "my class is ignored" and "my class is wrong" — and it is almost always the former.

The extraction rule that costs hours

The extractor scans the string literals of 4D, HTML and JavaScript files, but it only looks at a string whose first characters match a known Tailwind or DaisyUI prefix. The matching expressions are anchored at the start of the string.

$vt_Class:="flex gap-2 w-full"      // scanned: starts with "flex"
$vt_Class:="my-widget flex gap-2"   // IGNORED ENTIRELY: "my-widget" is not a known prefix

Trap: putting a house class first silently removes every utility class of that string from the stylesheet at the next rebuild. Blocks that had been fine for months lose their layout — buttons stretched across the full width are the classic symptom. Put your own class last, or in a separate string.

Editing the Tailwind source

Changing the tailwind.css source does not require a rebuild: refreshing the Storage is enough. A full rebuild is only needed when the set of classes in use changes — and it must be run from the application's dependency folder.

A "flat" cube still exists in migrated databases

A database coming from V20 may hold a cube missing its variant level. Validation spots the misplaced value and skips it rather than raising, because reshaping the cube is a separate job.

Worth knowing: that silence is not approval. A cube left flat renders unpredictably; normalising the cube is what repairs it, not simply saving again from the panel.

What comes next

Everything that is not styling belongs to The properties contract. The targets declared by a given type are listed on its own page, under Blocks.