../

Fullscreen

The Fullscreen API (requestFullscreen, exitFullscreen, fullscreenElement) and its CSS hooks, the iPhone video-only exception, plus two APIs that usually come with it: Screen Orientation lock and Screen Wake Lock. Support data is from MDN as of September 2026.

Support

FeatureStatus (MDN)Notes
Fullscreen API (unprefixed)not BaselineChrome 71, Firefox 64, Safari 16.4; not iPhone
iPad Safari16.4+ unprefixed, 12+ webkitforced overlay button; swipe down exits
iPhone Safari<video> onlyvideo.webkitEnterFullscreen()
navigationUI optionChromium, Safari 16.4Firefox ignores it
::backdropBaseline widely availablealso used by <dialog> and popovers
Screen Orientation (type, angle, change)Baseline widely available (2025)
screen.orientation.lock()not BaselineChrome Android, Firefox 144+; not Safari
Screen Wake LockBaseline 2025iOS 18.4 for home-screen apps

Requirements

RequirementDetail
transient activationrequestFullscreen() only from a click, key press or similar
iframes<iframe allow="fullscreen"> (legacy allowfullscreen); Permissions Policy fullscreen
connected elementthe element must be in the document; <dialog> and popovers work too
one at a timerequesting another element swaps which one is fullscreen
exitEsc always exits; the page can't block it
F11 / browser menubrowser fullscreen, not the API: fullscreenElement stays null

API

MemberType / returnsNotes
el.requestFullscreen(options?)Promise<void>rejects with TypeError when refused
document.exitFullscreen()Promise<void>rejects if nothing is fullscreen
document.fullscreenElementElement | nullalso on ShadowRoot for elements inside it
document.fullscreenEnabledbooleanfalse if the policy or platform forbids it
declare const player: HTMLElement;
 
async function toggle(el: HTMLElement) {
  if (document.fullscreenElement) {
    await document.exitFullscreen();
  } else {
    await el.requestFullscreen({ navigationUI: "hide" });
  }
}
EventTargetWhen
fullscreenchangethe element, bubbles to documententered or left, by code, Esc or swipe
fullscreenerrorthe element, bubbles to documenta request was refused (the promise rejects too)

Listen on document so you catch changes from every element and from Esc.

Options

FullscreenOptionsValuesSupportEffect
navigationUI"auto", "hide", "show"Chromium, Safari 16.4hide or keep the browser's navigation bar (mobile)
screenScreenDetailedChrome 100 desktopfullscreen on another monitor (Window Management API)
keyboardLock"none", "browser", "system"Firefox 151, Safari 26.4non-standard; capture Esc and system keys

screen and keyboardLock aren't in lib.dom; widen the options type if you use them.

CSS

/* the element while it is fullscreen */
.player:fullscreen {
  width: 100vw;
  height: 100dvh;
  background: black;
}
 
/* everything behind it (black by default) */
.player::backdrop {
  background: rgb(0 0 0 / 0.9);
}
 
/* style the page around it; :modal matches it too */
:root:has(:fullscreen) .site-header {
  display: none;
}
FactDetail
top layerthe element renders above everything; z-index is irrelevant
UA stylesposition: fixed, inset: 0, width/height: 100%, all !important; plus object-fit: contain
ancestors:fullscreen matches only the element itself, not its parents
prefixed:-webkit-full-screen for Safari before 16.4 and iPadOS 12–16.3

See CSS for :has() and the top layer.

iPhone & older Safari

iPhone Safari has no element fullscreen at all: requestFullscreen is undefined and document.fullscreenEnabled is never true. The only option is the native video player.

Member (on HTMLVideoElement)Meaning
webkitEnterFullscreen()opens the native player; call after loadedmetadata
webkitExitFullscreen()closes it
webkitDisplayingFullscreenboolean
webkitSupportsFullscreenboolean; false until metadata is loaded
webkitbeginfullscreen / webkitendfullscreenevents on the <video>

Custom controls, overlays and canvas games can't go fullscreen on iPhone. The closest thing is a web app installed to the Home Screen with manifest "display": "fullscreen" or "standalone".

type WebkitVideo = HTMLVideoElement & {
  webkitEnterFullscreen?: () => void;
};
 
function videoFullscreen(video: WebkitVideo) {
  if (video.requestFullscreen) {
    return video.requestFullscreen();
  }
  video.webkitEnterFullscreen?.(); // iPhone
}

Screen Orientation

MemberType / returnsNotes
screen.orientation.typeOrientationType"portrait-primary", "landscape-secondary", …
screen.orientation.anglenumber0, 90, 180 or 270
change eventEventon screen.orientation
screen.orientation.lock(type)Promise<void>mobile, in fullscreen
screen.orientation.unlock()voidback to the user's setting
lock() valueLocks to
"landscape" / "portrait"either landscape / either portrait
"landscape-primary" etc.one exact orientation
"natural"the device's natural orientation
"any"all four; undoes a previous lock
lock() rejectionCause
NotSupportedErrordesktop, Safari, or not fullscreen on Chrome Android
SecurityErrorpage hidden, or a sandboxed iframe without allow-orientation-lock
AbortErroranother lock() started, or unlock() was called meanwhile

For layout, prefer CSS @media (orientation: landscape) over the API. For installed apps, the manifest orientation member sets a default without fullscreen.

Screen Wake Lock

Keeps the screen on while visible content matters: recipes, presentations, boarding passes, video calls. It's a screen lock only; there's no CPU lock on the web.

MemberType / returnsNotes
navigator.wakeLock.request("screen")Promise<WakeLockSentinel>secure context; page must be visible
sentinel.release()Promise<void>release early
sentinel.releasedbooleantrue once released by you or the browser
release eventEventfires on the sentinel
Behavior / errorDetail
page hiddenthe browser releases the lock; request a new one on visibilitychange
NotAllowedErrorpage hidden, not fully active, blocked by policy screen-wake-lock, or low battery
Permissions API"screen-wake-lock"; granted without a prompt in practice
iOS home-screen appswork from iOS 18.4; before that only in Safari tabs

Typing in TS

SituationFix
el.requestFullscreen typed as always presenttest typeof el.requestFullscreen === "function" for iPhone
webkitEnterFullscreen, webkitRequestFullscreennot in lib.dom; intersect with an optional member type
document.fullscreenElementElement | null; not generic, so narrow with instanceof
screen.orientation.lock()missing from TS 5.x lib.dom (present in TS 7); widen the type as in the recipe
wake lockWakeLockSentinel, WakeLockType ("screen")

Recipes

Toggle fullscreen helper

A framework-free helper: state, toggle and a subscription, with the iPhone case reported as unsupported.

fullscreen.ts
export const fullscreenSupported = () =>
  document.fullscreenEnabled === true;
 
export const isFullscreen = (el?: Element) =>
  el
    ? document.fullscreenElement === el
    : document.fullscreenElement !== null;
 
export async function toggleFullscreen(el: HTMLElement) {
  if (!fullscreenSupported()) return false;
  if (isFullscreen(el)) await document.exitFullscreen();
  else await el.requestFullscreen({ navigationUI: "hide" });
  return true;
}
 
export function onFullscreenChange(cb: () => void) {
  document.addEventListener("fullscreenchange", cb);
  return () =>
    document.removeEventListener("fullscreenchange", cb);
}

useFullscreen hook

The same helper wired into React with useSyncExternalStore, so every component sees one truth.

import { useCallback, useSyncExternalStore } from "react";
import type { RefObject } from "react";
import {
  isFullscreen,
  onFullscreenChange,
  toggleFullscreen,
} from "./fullscreen.ts";
 
export function useFullscreen(
  ref: RefObject<HTMLElement | null>,
) {
  const active = useSyncExternalStore(
    onFullscreenChange,
    () => (ref.current ? isFullscreen(ref.current) : false),
    () => false, // server render
  );
  const toggle = useCallback(async () => {
    if (ref.current) await toggleFullscreen(ref.current);
  }, [ref]);
  return { active, toggle };
}

Fullscreen video with orientation lock

Landscape playback on Android; the native player on iPhone; plain fullscreen on desktop.

type Video = HTMLVideoElement & {
  webkitEnterFullscreen?: () => void;
};
// lock() is missing from TypeScript 5.x lib.dom
type Lockable = ScreenOrientation & {
  lock?(type: "landscape" | "portrait"): Promise<void>;
};
 
export async function playFullscreen(video: Video) {
  if (typeof video.requestFullscreen !== "function") {
    video.webkitEnterFullscreen?.(); // iPhone
    return video.play();
  }
  await video.requestFullscreen({ navigationUI: "hide" });
  const orientation = screen.orientation as Lockable;
  await orientation.lock?.("landscape").catch(() => {
    // desktop or Safari: no lock, that's fine
  });
  await video.play();
}
// on exit, call screen.orientation.unlock()

Wake lock during a presentation

Keep the screen on while a slideshow runs, re-acquiring after tab switches.

export function keepAwake() {
  let sentinel: WakeLockSentinel | null = null;
  let wanted = true;
  const acquire = async () => {
    if (!wanted || document.hidden) return;
    try {
      sentinel = await navigator.wakeLock.request("screen");
    } catch {
      // NotAllowedError: battery saver, policy
    }
  };
  const onVisible = () => void acquire();
  document.addEventListener("visibilitychange", onVisible);
  void acquire();
  return async () => {
    wanted = false;
    document.removeEventListener(
      "visibilitychange",
      onVisible,
    );
    await sentinel?.release();
  };
}

Call keepAwake() when the presentation starts and the returned function when it ends.

References