Viewport Segments: The Foundation

Nearly three years after dual-screen devices first shipped, the web platform now has stable primitives for building layouts that account for the physical gap or hinge between displays. What's notable about these APIs is how deliberately they build on existing responsive design concepts. The CSS media features and JavaScript interfaces used for foldable and dual-screen support look and behave like familiar tools for targeting different screen sizes, rather than introducing a separate paradigm to learn.

Two generations of Samsung Galaxy Fold and Z Flip devices, along with the Surface Duo and Duo 2, are already in consumers' hands. That install base makes dual-screen and foldable support a practical consideration, not an experimental one. The APIs described below were shaped by developer feedback gathered during origin trials; the result is detection and layout tooling that integrates with media queries, the viewport, and environment variables.

Detecting Dual-Screen Postures In CSS

Foldable detection uses a new media feature that describes how many distinct viewport regions exist in each axis. Like other media features, you can combine it with width queries to scope styles precisely.

horizontal-viewport-segments

The horizontal-viewport-segments feature reports the number of viewports present in the horizontal direction. A device in a book-like posture with a vertical hinge yields two viewports split into columns.

A foldable device illustration with displays side-by-side
The horizonal-viewport-segment targets the device when the hinge is in the vertical fold posture. (Large preview)

The following rule targets a foldable in that vertical-fold posture:

@media (horizontal-viewport-segments: 2) {
// Styles specific to the device in this orientation
}

The integer return value indicates the number of distinct viewports for the device orientation. With the device in vertical-fold posture (like an open book), there are two viewport regions horizontally and one vertically. You can also combine posture detection with viewport width checks to conditionally apply a more specific set of styles:

@media (horizontal-viewport-segments: 2) and (min-width: 540px) {
    body {
        background: yellow;
    }
}

vertical-viewport-segments

For the opposite orientation, vertical-viewport-segments detects when the hinge is horizontal and splits the screen into stacked rows.

A foldable device illustration with displays stacked on top of each other
The vertical-viewport-segments targets the device in the horizontal fold posture. (Large preview)

To target a device rotated into that posture, use this query:

@media (vertical-viewport-segments: 2) {
    // Styles specific to the device in this orientation
}

Detection From JavaScript

CSS media queries aren't always the right fit. In canvas-based contexts like WebGL or Canvas2D, the layout isn't defined in CSS, so you need programmatic access to the display geometry. The initial proposal for a dedicated Windows Segments Enumeration API was eventually replaced by an extension to the existing Visual Viewport API.

The segments property on visualViewport returns an immutable snapshot of the current display regions:

const segments = window.visualViewport.segments;

The result is an array of DOMRect objects, one per distinct viewport segment, when the window spans multiple displays. If only one display region exists, the API returns null — deliberately avoiding a truthy single-element array so developers won't start treating visualViewport.segments[0] as a standard single-screen path.

Because this is a snapshot, any change to the window or device invalidates the stored values. A browser window resized down to a single display region fires a resize event. Rotating the device fires both resize and orientation events. In either case, re-querying the property is required to obtain the updated state:

window.addEventListener("resize", function() {
    const segments = window.visualViewport.segments;
    console.log(segments.length); *// 1*
});

Choose the JavaScript API over the CSS media features when a project has no CSS layout to hook into — a game engine rendering to a canvas is the canonical example.

Environment Variables For Segment Geometry

Beyond posture detection, six CSS environment variables provide the actual coordinates and dimensions of each display region. These cover segment width and height, plus top, right, bottom, and left offsets:

  • env(viewport-segment-width <x> <y>)
  • env(viewport-segment-height <x> <y>)
  • env(viewport-segment-top <x> <y>)
  • env(viewport-segment-left <x> <y>)
  • env(viewport-segment-bottom <x> <y>)
  • env(viewport-segment-right <x> <y>)

The x and y parameters identify a segment within a two-dimensional grid created by hardware separators. Coordinate 0,0 resolves to the top-left segment.

The environment variables laid out on each display screen with the integers for each display
Environment variables define the position and dimensions of a logically separate region of the viewport. (Large preview)

Interpretation depends on pose. In vertical-fold posture, the left viewport is env(viewport-segment-width 0 0) and the right is env(viewport-segment-width 1 0). Rotated to horizontal-fold posture, the top is env(viewport-segment-height 0 0) and the bottom is env(viewport-segment-height 0 1).

The width and height variables accept an optional fallback value appended after the indices, but including it is your choice:

env(viewport-segment-width 0 0, 100%);

Computing The Hinge Area

For hardware hinges that occlude part of the screen, you can derive the obscured gap by comparing segment edges. A test with the viewport in vertical posture earns the hinge width by subtracting the left edge of the right segment from the right edge of the left segment:

A foldable device in the horiztonal position
We can calculate the device hinge with the environment variables. (Large preview)
calc(env(viewport-segment-left 1 0) - env(viewport-segment-right 0 0));

Positioning Against The Hinge

These environment variables are particularly useful for anchoring content to the inner edge of a display region. Take a design that should pin an image against the right side of the left viewport — exactly where a hinge or fold begins. Using the left property with viewport-segment-right might look like this:

img {
    max-width: 400px;
}

@media (horizontal-viewport-segments: 2) { 
    img {
        position: absolute;
        left: env(viewport-segment-right 0 0);
    }
}

Emulating Surface Duo in Edge Developer Tools shows the problem with a naive approach:

A Surface Duo emulated layout in the browser with an image on the right display and text on the left
Initially placing the image in our layout with the environment variable puts it in the wrong display region. (Large preview)

Because absolute positioning with left aligns the element's leftmost edge to the specified coordinate, the outcome lands in the wrong display entirely. Countering that offset is straightforward: subtract the element's own width from the environment variable:

img {
    max-width: 400px;
}

@media (horizontal-viewport-segments: 2) { 
    img {
        position: absolute;
        left: calc(env(viewport-segment-right 0 0) - 400px);
    }
}
A layout in the Surface Duo emulated in the browser with text and image both in the left display
Subtracting the image width from the viewport segment will place it along the hinge in the left display. (Large preview)

The result is the intended hinge-aligned placement. For further placement patterns along the seam, a simple box demo is available; open Edge Developer Tools > Device Emulation, choose Surface Duo, and confirm the Duo emulation posture matches the one you're testing.

Adapting A Recipe Page To A Dual-Screen Device

A recipe site that reflows to fit a dual-screen device is a practical use case for these new layout APIs. Let’s examine how to adapt a single recipe page, starting with how content is chunked on a conventional desktop layout.

The core content for a typical recipe minimal set includes the title, servings, prep time, images, ingredients, and steps. The initial wireframe calls for the title and details on top, a full-width image below that, and then the ingredients and steps in two columns underneath. The decision to use two columns here avoids substantial white space to the right of the ingredient list.

A sketch of how my website should be laid out on a desktop
A standard layout for a recipe page on desktop. (Large preview)

Choosing A Layout Method

Several options exist for coding the desktop layout, such as combining flebox for grouped content with CSS Grid for the page structure. However, the grouping must account for the target dual-screen experience.

A sketch of how my website should be laid out on a dual screen device
The ideal layout on a foldable device in the vertical fold position splits the con-tent up by display so it isn’t obscured by the hinge. (Large preview)

If the ingredients and steps are wrapped in a flex container, adapting them to sit apart on a dual screen would leave a large empty gap where the image column doesn't align.

A sketch of how my website would be laid out if I used flexbox, creating a large gap between content on a dual screen device
If I only use flexbox for this layout, it’s going to create some spacing gaps I want to avoid having to hack around. (Large preview)

CSS Grid alone provides the most control. Setting up the content and structure is straightforward:

<main>
    <section class="recipe">
        <div class="recipe-meta">
            … <!—Contains our recipe title, yield and servings --> 
        </div>
        <img src="imgs/pasta.jpg" alt="Pasta carbonara photographed from above on a rustic plate" />
        <div class="recipe-details__ingredients">
            …<!— Contains our ingredients list --> 
        </div>
        <div class="recipe-details__preparation">
            … <!— Contains our list of steps to put the ingredients together -->
        </div> 
    </section>
</main>

Define a three-column grid where each column is an equal fraction of the container, using grid-auto-rows with minmax so rows have a minimum height of 175px but can expand as needed.

.recipe {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(175px, max-content);

Add the remaining layout properties, such as gap, max-width, and auto-centering margins.

grid-gap: 1rem;
max-width: 64rem;
margin: 0 auto;
}

Placing the content items into their grid areas produces the intended desktop layout.

.recipe-meta {
    grid-column: 1 / 4;
}

.recipe-meta p {
    margin: 0;
}

img {
    width: 100%;
    grid-column: 1 / 4;
}

.recipe-details__ingredients {
    grid-row: 3;
}

.recipe-details__preparation {
    grid-column: 2 / 4;
    grid-row: 3;
}
Screenshot of my website laid out on desktop with CSS Grid guides turned on
The layout renders as expected on desktop. (Large preview)

Making The Layout Work Around The Hinge

Without further adjustments, the same layout on a dual screen would have content obscured by the hardware hinge.

Screenshot of my website emulated on a Surface Duo
Without any dual screen code implemented, if a user wants to span the browser across two displays, this is what the page would look like. (Large preview)
Screenshot of my website emulated on a Surface Duo showing content being ob-scured by the device hinge
The content is obscured by the hinge if a user chooses to span across both displays. (Large preview)

The first fix is redefining the grid columns to use the CSS environment variable for viewport segments. The first column spans the full width of the left display area, while the other two columns fill the right display.

@media (horizontal-viewport-segments: 2) { 

/* Body styles for smaller screens */
body {
        font: 1.3em/1.8 base, 'Playfair Display', serif;
        margin: 0;
    }

.recipe {
    grid-template-columns: env(viewport-segment-width 0 0 1fr 1fr;
    grid-template-rows: repeat(2, 175px) minmax(175px, max-content);
}

}

For the rows, set the first two rows to a fixed 175px height to contain the top details, then let the rest behave as defined initially. The previously set width and margin on the container will incorrectly pull the gridlines away from the segment edges, so those need to be reset.

Screenshot of my website on a Surface Duo with the layout split between displays, so no content is obscured
After adding my code, my content is no longer obscured but still needs some spacing adjustments. (Large preview)
@media (horizontal-viewport-segments: 2) { 

.recipe {
    grid-template-columns: env(viewport-segment-width 0 0) 1fr 1fr;
    grid-template-rows: repeat(2, 175px) minmax(175px, max-content);
    margin: 0;
    max-width: 100%;
}

}
Screenshot of my website on a Surface Duo with margin and padding reset
Resetting my margin and padding obscures content in the right display. (Large preview)

Finally, position the content within this new grid. The image is allowed to span the entire viewport width. For content below the image—which now starts at the gridline under the hinge—extra padding is required to visually match the padding from the left edge. A 3rem left padding compensates for the existing 1rem grid gap and the hinge offset.

.recipe-meta {
    grid-column: 1 / 2;
    padding: 0 2rem;
}

img {
    grid-column: 2 / 4;
    grid-row: 1 / 3;

    width: 100%;
    height: 100%;
    object-fit: cover;
    /* necessary to keep the image within the grid lines */
}

.recipe-details__ingredients {
    grid-row: 2;
    padding: 0 2rem; 
}

.recipe-details__preparation {
    grid-column: 2 / 4;
    grid-row: 3; 
    padding: 0 2rem 0 3rem; 
}
Final adjusted layout with padding and margin added back in to my layout for a dual screen device
I can re-add more appropriately sized padding to display content, so it isn’t obscured on a device with a physical hinge. (Large preview)

This yields a fully responsive dual-screen layout with only modest CSS changes using the new media feature. For small single-screen devices, the fallback layout is simple flexbox in a single column.

@media (max-width: 48rem) {

    body {
        font: 1.3em/1.8 base, 'Playfair Display', serif;
    }

    .recipe-details {
        display: flex;
        flex-direction: column;
    }

}

Availability And Testing

These dual-screen APIs are enabled by default in Microsoft Edge (and Edge on Android) starting with version 97. Other Chromium browsers don’t have a confirmed release date but will likely support them soon. In Chrome, you can test by enabling chrome://flags → experimental web platform features.

If you lack a physical device, browser developer tools are reliable. Emulation, particularly for the Surface Duo, matches the device experience closely. For non-supporting browsers, a polyfill exists for the Visual Viewport segments API. The CSS media queries have no equivalent polyfill.

Current dual-screen devices run Android, so users can choose to span a site across one screen or both. A site without dual-screen styling still works as a regular single-screen layout if the user opts for that view. However, the APIs allow you to progressively enhance for the device’s full capabilities.

From a practical standpoint, integrating these features with existing sites is a natural extension of responsive design. Microsoft’sSurface Duo documentation offers additional guidance. Emerging display technologies signal an opportunity for more creative and flexible web layouts.

Further Resources

For the live demo and to see the code in action, visit the demo site on Edge or a Chromium browser with it enabled under the aforementioned flags. This exploration shows that responsive design is evolving to encompass new device shapes, making dual screens the next frontier in web layout.