Skip to content

CSS and Layout

DooDesch edited this page Jul 27, 2026 · 1 revision

CSS and Layout

🛟 Need help or found a bug? Get support at support.doodesch.de/sideload.

The supported list is finite and comes straight from the engine's own property switch. Anything not on it is parsed and dropped silently - no error, no warning. That is fast to work with and unforgiving if you guess.

Design against 733 x 400 CSS pixels landscape, 400 x 733 portrait. The short side is always 400 CSS px whatever the real panel measures, so one stylesheet fits every resolution.

Supported

Box: display (flex/block/inline-block all mean flex, plus none), width, height, min-width, min-height, max-width, max-height, padding, margin, border, border-width, border-color, border-radius and every per-side and per-corner longhand, position: absolute/relative/static, top, right, bottom, left, inset, overflow, overflow-y, opacity.

Flex: flex, flex-grow, flex-shrink, flex-basis, flex-direction, flex-wrap, justify-content, align-items, align-self, gap, row-gap, column-gap.

Paint: background, background-color, linear-gradient() with exactly two colour stops and an optional leading angle, box-shadow (outer only), color.

Text: font-family, font-size, font-weight, font-style, line-height, text-align, white-space (nowrap), text-overflow: ellipsis, letter-spacing.

Motion: transform (translate, scale, rotate), transition, transition-duration, transition-delay, transition-timing-function, transition-property. Layout-free properties only - opacity, color, background-color, border-color and the transforms. An interrupted tween continues from the frame on screen rather than snapping.

Other: custom properties and var(), !important, inline style attributes, @media (orientation: portrait|landscape).

Units are px and % only.

Not supported - say so rather than trying

box-sizing (border-box is always in force), align-content, place-*, CSS Grid, float, display: inline/table, z-index (paint order is document order), animation/@keyframes, cursor, visibility, outline, text-decoration, text-transform, background-image: url(), background-size, background-position, background-repeat, filter, backdrop-filter, inset box-shadow (the inset keyword makes the whole declaration drop), hsl(), em, rem, vh, vw, calc(), and any media query other than orientation.

Selectors

Everything AngleSharp's querySelectorAll accepts: type, class, id, descendant, child, attribute, :not, :nth-child. Plus the state pseudo-classes :hover, :active, :focus, :focus-visible, :focus-within, :disabled - only on the last compound. .card:hover .title matches, but the hover part is ignored, so the rule applies all the time.

State rules repaint, they do not re-lay-out. :hover changing background, border-color or color works. :hover changing width, padding or display does not.

Cascade order: !important > inline style > specificity > document order.

Four rules that differ from a browser

  1. Border-box everywhere. width includes padding and border. Declaring box-sizing does nothing.
  2. Auto margins are zero. margin: 0 auto does not centre. Use justify-content, align-items or align-self - the same trade React Native makes.
  3. align-content is not implemented. Wrapped lines stack tightly from the cross start with the cross gap between them.
  4. Height never feeds back into width. Widths resolve first, text is measured against a known width, and the resulting height can never change a width. That is what keeps a layout pass finite instead of oscillating.

The automatic minimum, and why your list will not scroll

A flex item's min-height: auto resolves to its content height, per Flexbox 4.5. So a flex: 1 box refuses to shrink below its contents, and a scrollable list ends up as tall as everything in it - which means it never scrolls, and it pushes its siblings off screen.

The fix is the same one a browser needs:

.list {
  flex: 1;
  min-height: 0;   /* without this the box is as tall as its content and never scrolls */
  overflow: auto;
}

Along the main axis of a row, and inside a scroll container, the automatic minimum is already 0 - so this bites on columns, which is where lists live.

Quirks inside the supported set

  • line-height is measured, not drawn. Text height comes from TextMeshPro's own metrics, so setting line-height does not change the gap between rendered lines. Only an empty text leaf falls back to it.
  • box-shadow is outer only, and takes offset-x offset-y [blur] [colour]. Fewer than two lengths and the declaration is ignored.
  • linear-gradient takes exactly two colour stops plus an optional leading angle. A third is ignored. A gradient replaces the flat fill.
  • overflow-x never builds a horizontal scroll area. Only overflow-y: auto|scroll produces one, and it is vertical only. overflow-x still matters for the automatic minimum.
  • Symbol characters draw as empty boxes. The game's TextMeshPro atlases carry Latin text and little else, so arrows, checkmarks, dingbats - and (U+2026), which every truncation helper appends by reflex - come out as tofu. Write the word, or draw the shape with boxes. Use three full stops.
  • HTML tags with behaviour: input and textarea become a real TMP_InputField; button, a, input and textarea always get a hit target. head, script, style, title, meta and link are skipped.

Text compilation

An element whose children are only text and inline markup compiles to one TextMeshPro object, with b i strong em span turned into TMP rich text inside it. So a sentence with inline emphasis is one draw call and keeps its spaces.

An element with a mix of text and block children does not compile that way, and the text becomes its own leaf.

Fonts

font-family names are the game's own TextMeshPro font assets, scanned at startup. game-ui is the safe default. The full list is written to the log in a development build - see Dev Loop and Testing.

Clone this wiki locally