# How Ladybird Applies CSS Styling and Layout to Web Content: Inside the LibWeb Engine

> Discover how Ladybird applies CSS styling and layout via LibWeb's four-phase pipeline: parsing, selector matching, layout tree calculation, and painting. Learn the engine's process.

- Repository: [Ladybird/ladybird](https://github.com/LadybirdBrowser/ladybird)
- Tags: internals
- Published: 2026-03-05

---

**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`](https://github.com/LadybirdBrowser/ladybird/blob/main/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`](https://github.com/LadybirdBrowser/ladybird/blob/main/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`](https://github.com/LadybirdBrowser/ladybird/blob/main/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`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibWeb/SVG/SVGUseElement.cpp) implement this pattern:

```cpp
// 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`](https://github.com/LadybirdBrowser/ladybird/blob/main/BlockBox.h), [`InlineBox.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/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.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Layout/FlexFormattingContext.cpp) and [`Layout/GridFormattingContext.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Layout/GridFormattingContext.cpp)
- **Stacking context construction** – Built for z-index handling in [`Layout/StackingContext.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/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`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibWeb/ViewTransition/ViewTransition.cpp):

```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++:

```cpp
// 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:

```cpp
// 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:

```javascript
// 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`](https://github.com/LadybirdBrowser/ladybird/blob/main/LibWeb/CSS/Parser.cpp), producing `CSS::StyleSheet` objects containing rule trees.
- **Selector matching** runs in [`LibWeb/CSS/SelectorEngine.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/LibWeb/CSS/SelectorEngine.cpp), applying cascade rules to generate `CSS::ComputedProperties` stored on each element.
- **Layout tree construction** uses virtual `create_layout_node` methods (as seen in [`SVGUseElement.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/SVGUseElement.cpp)) to generate `Layout::Node` instances from DOM elements.
- **Layout calculation** executes depth-first in `Layout::Node::layout`, using formatting contexts like [`FlexFormattingContext.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/FlexFormattingContext.cpp) and [`GridFormattingContext.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/GridFormattingContext.cpp) to resolve geometry.
- **Dynamic updates** modify `ComputedProperties` and mark layout nodes dirty, with specialized handling for view transitions in [`ViewTransition.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/ViewTransition.cpp).

## Frequently Asked Questions

### How does Ladybird resolve CSS specificity and cascade conflicts?

Ladybird implements standard CSS cascade resolution in [`LibWeb/CSS/SelectorEngine.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/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`](https://github.com/LadybirdBrowser/ladybird/blob/main/FlexFormattingContext.cpp) and **CSS Grid** via [`GridFormattingContext.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/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.