min read

Styling a fullscreen PWA

The Web App Manifest is the format that defines metadata about an application, so that a user can add it to their homescreen.

One of the important properties in this file is display mode. For most types of applications, in order to behave like a native app, you would choose standalone. However, for some other types of applications where you would want the OS status bars to be hidden, you can select fullscreen. An example here would be a game.

Now, let's say your game includes a button that toggles fullscreen mode when in a normal webpage. The Fullscreen API allows for this by calling element.requestFullscreen() to go into fullscreen mode and document.exitFullscreen() to exit.

When the app is running as a fullscreen PWA there is no need for this toggle button, and in fact it won't work. You can't exit fullscreen when in this display mode. So best thing to do is to hide it.

The fullscreen API includes a :fullscreen pseudo-class. It can be used to target elements that are in fullscreen. However, this won't do what you want:

.root:fullscreen #fullscreen-button {
  display: none;
}

The problem here is that when the app is running in a normal browser window we don't want to hide the button when in fullscreen mode. So how can we target when the app is running in the fullscreen display mode, per the manifest file?

The spec authors were clever enough to think of this and gave us the display-mode media query. It allows targeting specifically when in fullscreen display mode, per running as a PWA. You use it like this:

@media all and (display-mode: fullscreen) {
    
}

However, this is not the answer to our dilemma:

@media all and (display-mode: fullscreen) {
  .root #fullscreen-button {
    display: none;
  }
}

The problem with the above is that on some OSes (Android at least), the window is in fullscreen display mode whenever you enter fullscreen; whether that be full the manifest file or from using element.requestFullscreen() in a browser window. Since we want to display the button when you get to fullscreen from a browser window, we can't use this approach.

The trick is, when running as a PWA, the display-mode will be fullscreen but the :fullscreen pseudo-class will not match. I'm not entirely clear on why this is, but I'm assuming it is because that class only matches when the JavaScript API is used to enter fullscreen.

With all of that being said, here is our final solution:

@media all and (display-mode: fullscreen) {
  .root:not(:fullscreen) #fullscreen-button {
    display: none;
  }
}

This says to match when the .root not does not match the fullscreen selector, but is in fullscreen display-mode. And that's it!