How Safari Implements CSS Grid and Subgrid for Web Layouts
This article provides a technical breakdown of how the Safari web browser implements CSS Grid and CSS Subgrid to render complex two-dimensional webpage layouts. It examines the underlying mechanics of Safari's WebKit rendering engine, detailing how the browser parses grid properties, executes the track sizing algorithm, and manages layout calculations. Additionally, it explains how Safari resolves nested layout dependencies through CSS Subgrid and optimizes rendering performance for modern web applications.
The WebKit Rendering
Engine and RenderGrid
Safari relies on the open-source WebKit layout engine to convert HTML
and CSS into pixels on the screen. When Safari encounters an element
with display: grid or display: inline-grid,
WebKit instantiates a specific C++ class in its render tree called
RenderGrid.
The RenderGrid object is responsible for managing the
two-dimensional coordinate system. Unlike traditional block or inline
layouts that process elements sequentially along a single axis,
RenderGrid evaluates both rows and columns simultaneously.
It achieves this by dividing the layout process into distinct phases:
item placement, track sizing, and alignment.
Step 1: Grid Item Placement
Before Safari can calculate the size of the grid, it must know where every child element (grid item) belongs. WebKit constructs an internal representation of the grid called the Grid Matrix.
- Explicit Placement: Safari first places items that
have defined coordinates (using properties like
grid-column-startorgrid-row). - Implicit Placement: For items without explicit
coordinates, Safari runs the "grid auto-placement algorithm." WebKit
iterates through the unoccupied cells in the Grid Matrix, placing items
according to the
grid-auto-flowproperty (either row by row, column by column, or filling in gaps using thedensekeyword). If there are not enough defined tracks to hold the items, WebKit dynamically generates implicit tracks to accommodate them.
Step 2: The Track Sizing Algorithm
Once all items are mapped to the Grid Matrix, Safari executes the CSS Grid Track Sizing Algorithm to determine the exact pixel dimensions of every row and column. WebKit processes tracks in the following order:
- Fixed Tracks: Columns and rows defined with absolute units (like pixels) or percentages that can be immediately resolved against the grid container’s size are calculated first.
- Intrinsic Tracks: Tracks using keywords like
min-content,max-content, orautorequire Safari to measure the actual content inside the grid items. WebKit queries the child elements for their minimum and maximum intrinsic sizes and expands the tracks accordingly. - Flexible Tracks (
fr): Finally, Safari calculates tracks using the fractional unit (fr). WebKit takes the remaining available space in the grid container—after subtracting fixed tracks, intrinsic tracks, and grid gaps—and distributes it proportionally among the flexible tracks.
To handle two-dimensional dependencies (where the width of a column might affect the height of text in a row), WebKit executes a multi-pass layout cycle. It calculates column widths first, constrains the grid items to those widths to determine how content wraps, and then calculates the resulting row heights.
Implementing CSS Subgrid
Introduced in Safari 16, CSS Subgrid allows a nested grid item to adopt the tracks of its parent grid container. Without Subgrid, nested grids operate as entirely independent layout contexts, making it impossible for grandchild elements to align with the main parent grid based on content size.
When Safari encounters grid-template-columns: subgrid or
grid-template-rows: subgrid on a child
RenderGrid object, it alters the standard layout boundary
rules:
- Shared Sizing Context: The subgrid does not run its
own independent track sizing algorithm for the subgridded dimension.
Instead, it binds its tracks directly to the parent
RenderGrid’s tracks that it spans across. - Hoisting Grandchild Metrics: During the intrinsic
sizing phase, WebKit "hoists" the sizing requirements of the subgrid's
children up into the parent grid's calculation. The parent grid treats
the subgrid's items as if they were its own direct children for the
purpose of calculating
min-content,max-content, andautotrack sizes. - Accounting for Box Model Offsets: A subgrid can
have its own margins, borders, and padding, as well as its own custom
gapsizes. Safari’s engine calculates the difference between the parent's gap and the subgrid's gap, alongside the subgrid's box-model edges. It then subtracts these values from the intrinsic space contributed by the subgrid's edge items, ensuring the nested items align perfectly with the outer tracks without overflowing.
Layout Optimization and Caching
Calculating two-dimensional layouts—especially with deeply nested subgrids—is computationally expensive. To maintain 60-frames-per-second (or 120fps on ProMotion displays) scrolling and animation performance, Safari employs aggressive caching strategies within WebKit:
- Intrinsic Size Caching: Measuring text and images
is the slowest part of grid layout. Safari caches the
min-contentandmax-contentsizes of grid items. If the viewport resizes, Safari re-runs the track sizing math using the cached content metrics rather than re-measuring the DOM nodes, provided the content itself hasn't changed. - Dirty-Bit Tracking: WebKit uses a system of "dirty bits" in the render tree. If a single grid item changes its content, Safari marks only that specific node and its immediate track dependencies as "dirty," preventing a full recalculation of the entire webpage layout.
Developer Tooling Integration
To help developers visualize this implementation, Safari includes a
dedicated Grid Overlay tool within the Web Inspector. This tool hooks
directly into the RenderGrid C++ objects, extracting the
final computed values of the Grid Matrix. It renders visual lines over
the webpage showing explicit tracks, implicit tracks, grid gaps, and
extended subgrid lines, reflecting the exact mathematical boundaries
WebKit used to paint the layout.