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
| Level | Values |
|---|---|
| target | The HTML element receiving the classes. bspk is the block itself and always exists; each type declares the others in its schema — input, label, button… |
| breakpoint | default, xs, sm, md, lg, xl, 2xl |
| variant | default, hover, focus, active, disabled, dark, plus the business variants a type declares — selected for the listbox and the tree |
| property | One 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.

