How to Create a Portrait Mode Only Mobile-First Web App

Not all native apps or mobile-first web apps are designed to work in both portrait and landscape orientations on mobile phones. While it would be nice to lock the screen orientation, there are no great ways to do that in a web app.

If you are developing a native app, this is easy. Check out this article.

Unfortunately, it is not so easy for mobile-first web apps. With the various screen sizes, device aspect ratios, and keyboard behaviors across platforms and browsers, you can spend hours pulling your hair out trying to get this to work. Believe me, I have.

Let me save you the tears and angry noises.

This post explores how to achieve a sane “X orientation mode only” solution for a mobile-first React web app. There are many ways to do this, and this method is not perfect.

Javascript or CSS?

There are some built in Javascript utilities for locking the screen orientation, such as screen.orientation.lock(orientation).

However, these are limited by both browser support and the need to operate in full-screen mode. Since we need iOS Safari as well as Android browsers, JavaScript solutions are too limited.

CSS it is!

CSS Media Queries

The @media (orientation:portrait) media query checks that the height of the screen is greater than or equal to the width. (See more details here: Media Queries Level 4)

Similarly, the `aspect-ratio` media query is the ratio of width to height of the media feature. The orientation media query is essentially an alias for @media screen and (min-aspect-ratio: 1/1) when checking for landscape mode.

Great! Except, not always. The problem you might run into when using either feature is that the height and width are of the viewport, not the device screen per se. So, when the soft keyboard is opened, for example, the height might be shorter that the width, which would evaluate to “landscape” mode. This problem also occurs when using a `aspect-ratio` media query.

After some research and looking around the internet, it looked like the better media query for detecting orientation barring differing soft keyboard and browser-specific Javascript behavior would be:
@media screen and (min-aspect-ratio: 13/9) { /* landscape mode */ }
@media screen and (max-aspect-ratio: 13/9) { /* portrait mode */ }

This worked for many of the standard phones. However, as device dimensions continue to drift, there are always exceptions. We call those the Problem Phones.

A Mostly Working Solution

Our exploratory tester, Phil Kirkham, takes great delight in hammering on our code from a desk full of phones of all shapes and sizes. He will tell you that we had Problem Phone after Problem Phone while getting this to work.

When the keyboard is opened on one of the Problem Phones, the viewport is 360×348, which is roughly an aspect ratio of 30/29. Because we are checking for a minimum aspect ratio on 13/9, this throws the app into landscape mode via the orientation query. To get around this, we want to say only compare the height and width if the width of the viewport is greater than 800px. This is an arbitrary number that is roughly greater than the majority of phone widths when held in portrait mode.

 

import {Grid, makeStyles, Typography} from "@material-ui/core";
import React from "react";

export const PortraitModeOnly: React.FC = props => {
  const classes = useStyles();
  return (
    <>
      <div className={classes.portraitMode}>{props.children}<div>
      <div className={classes.landscapeMode}>
        hey, flip your phone to portrait mode please!
      </div>
    </>
  );
};

const useStyles = makeStyles({
  portraitMode: {
    display: "none",
    "@media screen and (max-aspect-ratio: 13/9)": {
      height: "100%",
      width: "100%",
      display: "block",
    },
  },
  landscapeMode: {
    display: "none",
    "@media (max-width: 800px) and (min-aspect-ratio: 13/9)": {
      height: "100%",
      display: "block",
    },
  },
});

Achieving Portrait Mode

Now all you have to do is wrap your app with this component and choose what you want to display when the user is on the wrong screen orientation.

Conversation
  • Guy Elsmore-Paddock says:

    This solution doesn’t appear to be working for us with Android 12 phones.

    • Meredith Lind Meredith Lind says:

      Hi Guy, thanks for the comment. Given the wide variety of screen sizes, I am not surprised that this doesn’t work for every phone.

      First, I would dig into the particular screen sizes you are working with and adjust the 800px in the second media query accordingly.

      If that does not work, it may be a difference in how the specific browser calculates min-aspect-ratio. So I would look there.

      Hope this helps!

  • david says:

    ‘scuse my ignorance but isn’t just orientation: portrait / landscape enough?

    • Meredith Lind Meredith Lind says:

      Hi David! Thanks for the question.

      It certainly could be enough, depending on your use. The note on the MDN docs for the orientation media query points out the problem that I was running into when using it.

      Note: This feature does not correspond to device orientation. Opening the soft keyboard on many devices in portrait orientation will cause the viewport to become wider than it is tall, thereby causing the browser to use landscape styles instead of portrait.

      The media query calculated that the orientation had changed when users opened the soft keyboard even though they had not rotated their device. If that isn’t a problem for you, I would encourage you to try out using orientation: portrait / landscape!

  • Comments are closed.