How Ladybird Applies CSS Styling and Layout to Web Content: Inside the LibWeb Engine
Ladybird applies CSS styling and layout through a four-phase pipeline implemented in LibWeb: CSS parsing creates style sheets, selector matching produces computed properties, the layout tree calculates geometry using formatting contexts, and painting renders the final output.
Ladybird renders web content using a classic "Parse → Style → Layout → Paint" model implemented in its LibWeb component. This architecture separates style computation from geometry calculation, enabling efficient incremental updates when the DOM or CSS changes. The CSS styling and layout engine relies on specific C++ classes in the Libraries/LibWeb/ directory to transform raw HTML and CSS into rendered pixels.
The Rendering Pipeline Architecture
Ladybird's rendering engine follows a strict pipeline where each phase feeds into the next. First, the CSS parser processes style sheets into structured objects. Next, the selector engine matches rules to DOM elements and resolves the cascade. Then, the layout system creates a parallel tree of layout nodes that calculate positions and sizes. Finally, the paint phase draws these boxes to the screen according to stacking context rules.
Phase 1: CSS Parsing and Style Sheet Construction
When a document loads, Ladybird parses <style> elements and external style sheets in LibWeb/CSS/Parser.cpp. The parser generates CSS::StyleSheet objects containing trees of rules such as CSS::CSSStyleRule and CSS::CSSKeyframesRule.
These style sheets remain in memory as structured objects until the engine needs to determine which rules apply to specific DOM elements.
Phase 2: Selector Matching and Cascade Resolution
For each DOM element, Ladybird runs the selector-matching algorithm defined in LibWeb/CSS/SelectorEngine.cpp. The engine evaluates selectors against the element's attributes, parentage, and DOM state.
Matching rules are ordered according to CSS cascade rules: importance, origin, specificity, and document order. The final resolved values are stored in a CSS::ComputedProperties object that lives directly on the element. This data structure, defined in Libraries/LibWeb/CSS/ComputedProperties.h, holds all resolved style values for properties like display, margin, and background-color.
Phase 3: Creating the Layout Tree
When the document requires layout, each DOM element creates a corresponding layout node via a virtual create_layout_node method. This method instantiates specific Layout::Node subclasses based on the element type and computed display properties.
For example, SVG elements in Libraries/LibWeb/SVG/SVGUseElement.cpp implement this pattern:
// Libraries/LibWeb/SVG/SVGUseElement.cpp
GC::Ptr<Layout::Node> SVGUseElement::create_layout_node(GC::Ref<CSS::ComputedProperties> style) {
return heap().allocate<Layout::SVGGraphicsBox>(document(), *this, move(style));
}
Standard HTML elements follow the same pattern. An HTMLDivElement calls create_layout_node to return a Layout::BlockBox, while inline elements generate Layout::InlineBox instances. All layout node classes reside under Libraries/LibWeb/Layout/, including BlockBox.h, InlineBox.h, and specialized formatting context boxes.
Phase 4: Layout Calculation and Painting
Once the layout tree exists, each node executes its layout algorithm through Layout::Node::layout. This method reads computed style values—such as margin, padding, and display—through the CSS::ComputedProperties interface (e.g., computed_properties.display(), computed_properties.border_spacing_horizontal()).
The layout pass proceeds depth-first and includes:
- Box model calculations – Resolving widths, heights, and positions from cascade values
- Flex and Grid algorithms – Handled in
Layout/FlexFormattingContext.cppandLayout/GridFormattingContext.cpp - Stacking context construction – Built for z-index handling in
Layout/StackingContext.cpp
After layout completes, each Layout::Node paints itself onto a bitmap through its paint() method, following the stacking context hierarchy to ensure correct z-ordering.
Handling Dynamic Style Changes
When JavaScript or the CSSOM mutates a style, Ladybird updates the element's CSS::ComputedProperties and marks the layout tree dirty. The next visual update triggers a re-layout of only the affected subtree.
The View Transitions API demonstrates this mechanism in Libraries/LibWeb/ViewTransition/ViewTransition.cpp:
// Libraries/LibWeb/ViewTransition/ViewTransition.cpp
ErrorOr<void> ViewTransition::update_pseudo_element_styles() {
// … creates/updates CSSStyleRule objects in a dynamic stylesheet.
}
This function injects dynamic CSS rules that the layout engine consumes on the next layout pass, ensuring animations and transitions recalculate geometry correctly.
Working with Ladybird's Style and Layout APIs
You can interact with Ladybird's styling system programmatically. To retrieve computed styles from C++:
// Retrieve the computed style of an element (C++ API)
auto& element = document.get_element_by_id("my-div");
auto const& computed = element.computed_css_values();
// Query a specific property
CSS::Color bg = computed.property(Web::CSS::PropertyID::BackgroundColor).to_color();
To force document layout and painting after programmatic changes:
// Force a layout of the whole document (e.g. after a style change)
document.layout(); // builds/updates the Layout::Node tree
document.paint_all(); // triggers painting
From JavaScript, style changes automatically trigger the dirty-checking mechanism:
// Change a style from JavaScript and let Ladybird re‑layout automatically
let el = document.getElementById('my-div');
el.style.setProperty('margin-left', '20px'); // updates ComputedProperties
// Ladybird marks the layout node dirty; the next frame runs layout+paint.
Summary
- CSS parsing happens in
LibWeb/CSS/Parser.cpp, producingCSS::StyleSheetobjects containing rule trees. - Selector matching runs in
LibWeb/CSS/SelectorEngine.cpp, applying cascade rules to generateCSS::ComputedPropertiesstored on each element. - Layout tree construction uses virtual
create_layout_nodemethods (as seen inSVGUseElement.cpp) to generateLayout::Nodeinstances from DOM elements. - Layout calculation executes depth-first in
Layout::Node::layout, using formatting contexts likeFlexFormattingContext.cppandGridFormattingContext.cppto resolve geometry. - Dynamic updates modify
ComputedPropertiesand mark layout nodes dirty, with specialized handling for view transitions inViewTransition.cpp.
Frequently Asked Questions
How does Ladybird resolve CSS specificity and cascade conflicts?
Ladybird implements standard CSS cascade resolution in LibWeb/CSS/SelectorEngine.cpp. The engine sorts matching declarations by importance (whether !important is present), origin (author vs. user vs. user-agent), selector specificity, and finally source order. The winning declarations populate the CSS::ComputedProperties object attached to each DOM element.
What is the relationship between DOM elements and layout nodes?
Each visible DOM element creates exactly one layout node through its create_layout_node method. This layout node holds a reference to the element's ComputedProperties and implements layout algorithms specific to its display type (block, inline, flex, or grid). The separation allows the DOM to change independently while the layout tree handles geometry calculations.
How does Ladybird optimize layout recalculation when styles change?
Ladybird uses a dirty-bit system. When JavaScript or CSSOM updates modify an element's ComputedProperties, the engine marks that element's layout node and its ancestors as dirty. The next frame runs a partial re-layout starting from the dirty nodes rather than recalculating the entire document tree, as implemented in the layout scheduling logic within Libraries/LibWeb/Layout/.
Which modern layout algorithms does Ladybird support?
According to the source code in Libraries/LibWeb/Layout/, Ladybird supports Flexbox via FlexFormattingContext.cpp and CSS Grid via GridFormattingContext.cpp. These implement the full algorithms for distributing space and aligning items within flex and grid containers, reading properties like justify-content and grid-template-columns from the computed style objects.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →