Layout and CSS
The CSS you already write
Layout is blitz-dom's: Stylo resolves the CSS and Taffy places the boxes, the same engines Dioxus's own native renderer uses. So the support is broad, and this page shows it through the examples rather than as a list of properties.
Stylesheets
Stylesheets go in HtmlConfig::stylesheets, applied in order after the user-agent
sheet, as if each were a <style> in <head>. The
style attribute works too. The user-agent sheet is blitz-dom's, much like a
browser's (body { margin: 8px }), and
HtmlConfig::user_agent_stylesheet replaces it when you want a blank start.
let dom = HtmlDom::with_config(app, HtmlConfig {
stylesheets: vec![STYLE.to_string()],
..HtmlConfig::default()
});
Block and inline flow
Blocks stack and their vertical margins collapse. In samples/hello the heading's
16px bottom margin and the paragraph's 12px top margin collapse to 16px, so the paragraph sits
at y = 96 rather than 108. A paragraph that mixes plain text, strong and
em is one inline formatting context with a run per style, all on one line.
p { id: "intro",
"This page is "
strong { id: "bold", "HTML" }
" and "
em { id: "italic", "CSS" }
", laid out by the Host."
}
Flexbox
samples/dashboard is flexbox from top to bottom: a column for the page, a row for
the top bar, a column for the sidebar and a wrapping row of cards. In a 1000px viewport three
200px cards fit a row; at 700px the row holds two and the rest wrap. Its tests check both.
.app { display: flex; flex-direction: column; height: 100vh; }
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
height: 56px;
}
.body { display: flex; flex: 1; align-items: stretch; min-height: 0; }
.cards {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: flex-start;
align-items: flex-start;
}
min-width: fit-content works as well. In samples/workbench a tab is
120px wide unless its name needs more, so layout_engine.rs gets 185px while
main.rs keeps 120px.
Grid
samples/gallery uses repeat(auto-fill, minmax(160px, 1fr)). In an
800px viewport that is four 183px columns, and a tile with grid-column: span 2
covers two of them (378px).
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
grid-auto-rows: 140px;
gap: 12px;
padding: 16px;
}
.tile.wide { grid-column: span 2; }
Tables
table, thead, tbody, tr, th
and td lay out as a table. In samples/table the column widths come
from the header cells, border-collapse: collapse shares borders, and
text-align: right puts the amounts against the right edge. The table is at least
640px wide, so on a 480px page it scrolls sideways inside its wrapper (see
Scrolling and overlays).
Positioning and stacking
position: relative, absolute and fixed work, and
z-index decides both the paint order and which box a click lands on. In
samples/overlay a menu is placed at top: 100% of its button's
wrapper and paints over the content below it, and a dialog is fixed to the centre of the
viewport over a fixed backdrop.
.menu-anchor { position: relative; display: flex; }
.menu { position: absolute; top: 100%; left: 0; z-index: 10; width: 180px; }
.dialog {
position: fixed;
top: 50%;
left: 50%;
z-index: 101;
width: 320px;
transform: translate(-50%, -50%);
}
The dialog's translate(-50%, -50%) works because translations are added to the
box's position. Other transforms are not drawn yet; see below.
Backgrounds, borders and effects
Every box carries its resolved decorations into the display list: background colour and
background images (Images and backgrounds), borders per side with
their own widths and colours, corner radii reduced the way CSS reduces radii that would
overlap, box-shadow, and opacity multiplied down through its
ancestors. visibility: hidden keeps a box's place and draws nothing, and
display: none removes it.
.card {
width: 200px;
height: 120px;
padding: 16px;
background: #ffffff;
border: 1px solid #d0d7de;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(31, 35, 40, 0.2);
}
.card.alert { border-left: 4px solid #cf222e; }
.card.stale { opacity: 0.6; }
What the renderer will receive
The renderer does not get CSS. plan_from turns the display list into drawing
elements, and the choices it makes keep that tree small:
- Equal borders become one border; unequal ones become one per side. Equal radii become one shape; unequal ones one radius per corner.
- Absolute positions become offsets inside the parent box, so a moved box is one offset change rather than a change to every descendant.
overflow: autoorscrollbecomes a scroll container holding its content.opacitybecomes one alpha on the group, andz-indexbecomes child order.- After an update,
diffreports only what changed. Changing one box's background is one change in the plan, and a test checks exactly that.
Not yet
| CSS | Today | Status |
|---|---|---|
transform: rotate, scale, skew, matrix(), transform-origin | Stylo computes them; only translations reach the box. A box with any other transform stays where layout put it. | planned #46 |
transition, animation, @keyframes | Every animated value stays at its first value. | planned #46 (opacity, transform, colours), #47 (width, height, margins) |
| Page zoom like VS Code's (Cmd/Ctrl + +) | The page is laid out at the scale you pass to layout. | planned |
Animations will be split by what they move. Properties that do not change layout (opacity, transforms, colours) will play in the renderer on Compose's own animation clock, so the Host does no work per frame. Properties that do change layout will be recomputed by the Host each frame, because only the layout engine can say where everything else moves.