CSS viewport segments media query for dual-screen detection

The spanning CSS media feature can be used to test whether the output device is a dual-screen (or foldable) and the browser viewport is spanned across the two display regions. Environment variables are also available to calculate the visible areas on the screens, and the hinge area (if any) between them.

This API is available in Microsoft Edge version 97 and newer.

Viewport Segments

CSS syntax to declare styles for when the browser is spanned across a horizontal or vertical hinge:

@media (horizontal-viewport-segments: <count>) { }
@media (vertical-viewport-segments: <count>) { }

For Surface Duo, the values that will match the dual-screen display are:

horizontal-viewport-segments: 2

  • Describes the state of when the browser viewport is spanning across a single fold (two display regions) and the fold posture is vertical. This value matches Surface Duo in double-portrait (wide) mode.

vertical-viewport-segments: 2

  • Describes the state of when the browser viewport is spanning across a single fold (two display regions) and the fold posture is horizontal. This value matches Surface Duo in double-landscape (tall) mode.

This example CSS snippet shows how to use the -viewport-segments media features to apply styles on a Surface Duo:

@media (horizontal-viewport-segments: 2) {
   /* styles applied in double-portrait (wide) mode */
   /* matches Figure 1. below */
}
@media (vertical-viewport-segments: 2) {
   /* styles applied in double-landscape (tall) mode */
   /* matches Figure 2. below */
}

Surface Duo's two orientations, double portrait and double landscape

CSS environment variables

Web developers can utilize browser-defined environment variables to get the geometry of the display region (or regions) on and calculate the geometry of the obscured hinge area (if present). The properties of each viewport can be queried with the following environment variable definitions (using the coordinates of the top-leftmost segment):

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 coordinates are assigned from the top-left segment:

CSS env variable coordinates example

These values can be used to infer the coordinates of hinge areas:

CSS env variables on a dual-screen device in double portrait mode

/* double-portrait */
env(viewport-segment-right 0 0);      /* hinge left */
env(viewport-segment-left 1 0);       /* hinge right*/
calc(env(viewport-segment-left 1 0) - env(viewport-segment-right 0 0)) 
                                      /* hinge width */
/* double-landscape */
env(viewport-segment-bottom 0 0);     /* hinge top */
env(viewport-segment-top 0 1);        /* hinge bottom */
calc(env(viewport-segment-top 0 1) - env(viewport-segment-bottom 0 0)) 
                                      /* hinge height */

Examples

Basic

Create a responsive page where the <body> background-color is set to yellow on phones and green on dual-screen devices in any posture.

Illustration of the basic example output

/* maximum width of our customers phones is 420px */
/* spanning: none is optional in this case */
@media (max-width: 420px) {
   body {
      background-color: yellow;
   }
}

/* Separating media features with comma `,` is equivalent to the logical operation OR  */
@media (horizontal-viewport-segments: 2), (vertical-viewport-segments: 2) {
   body {
      background-color: green;
   }
}

Flexbox

Use flexbox to create a gap-aware two column layout where the first column contains a scrollable description and the second column contains the image.

Dual-screen CSS demo

The HTML and CSS to create this layout is shown below:

   <body>
      <article class="article">
         ...
      </article>
      <figure class="figure">
         <img src="/sydney-opera-house.jpg"
               alt="Sydney Opera House">
      </figure>
   </body>
body {
   height: 100vh;
   display: flex;
}

.article {
   /* grow: no, shrink: no, basis: fold-left */
   flex: 0 0 env(viewport-segment-right 0 0);

   /* equals to margin-right when writing mode is left-to-right (english)  */
   /* equals to margin-left when writing mode is right-to-left (arabic, hebrew)  */
   /* this will prevent content from being rendered behind the device mask */
   margin-inline-end: calc(env(viewport-segment-left 1 0) - env(viewport-segment-right 0 0)) ; /* hinge width */

   overflow-y: scroll;
}

.figure {
   /* fill the rest of the space */
   flex: 1;

   margin: 0;
   overflow: hidden;
}

.figure img {
   height: 100%;
}