Fast page loads are only part of the UX equation. The transition between pages is just as visible—and often worse than the load itself. Multi-page applications (MPAs) force users to stare at a blank screen while the next document is fetched and rendered. Single-page applications (SPAs) replace that with smooth transitions, but at the cost of significantly more build complexity.

The proposed Portals API targets that gap: it aims to give MPAs the transition polish of SPAs without the architectural overhead. A <portal> behaves like an <iframe> in that it embeds another page, but unlike an <iframe>, it can be activated to navigate into that embedded content.

Seamless embeds and navigation with Portals. Created by Adam Argyle.

Basic usage and feature detection

A minimal implementation is surprisingly compact. The following example creates a portal, appends it to the document, and activates it with animation:

A gif of preview portal style demo

When the activate() call fires, the embedded page takes over the current tab. This produces a navigation with no blank intermediate state—the destination has already been rendered inside the portal element.

For feature detection, which is useful for progressive enhancement, check for the HTMLPortalElement constructor:

// Adding some styles with transitions
const style = document.createElement('style');
style.innerHTML = `
  portal {
    position:fixed;
    width: 100%;
    height: 100%;
    opacity: 0;
    box-shadow: 0 0 20px 10px #999;
    transform: scale(0.4);
    transform-origin: bottom left;
    bottom: 20px;
    left: 20px;
    animation-name: fade-in;
    animation-duration: 1s;
    animation-delay: 2s;
    animation-fill-mode: forwards;
  }
  .portal-transition {
    transition: transform 0.4s;
  }
  @media (prefers-reduced-motion: reduce) {
    .portal-transition {
      transition: transform 0.001s;
    }
  }
  .portal-reveal {
    transform: scale(1.0) translateX(-20px) translateY(20px);
  }
  @keyframes fade-in {
    0%   { opacity: 0; }
    100% { opacity: 1; }
  }
`;
const portal = document.createElement('portal');
// Let's navigate into the WICG Portals spec page
portal.src = 'https://wicg.github.io/portals/';
// Add a class that defines the transition. Consider using
// `prefers-reduced-motion` media query to control the animation.
// https://developers.google.com/web/updates/2019/03/prefers-reduced-motion
portal.classList.add('portal-transition');
portal.addEventListener('click', (evt) => {
  // Animate the portal once user interacts
  portal.classList.add('portal-reveal');
});
portal.addEventListener('transitionend', (evt) => {
  if (evt.propertyName == 'transform') {
    // Activate the portal once the transition has completed
    portal.activate();
  }
});
document.body.append(style, portal);

Trying Portals in Chrome

Portals are available for experimentation in Chrome 85 and later behind an experimental flag:

  • Enable about://flags/#enable-portals for same-origin navigations.
  • For cross-origin tests, also enable about://flags/#enable-portals-cross-origin.

Since this is an early experiment, it is recommended to run Chrome with a separate --user-data-dir flag when testing. Once enabled, you can verify the feature is active in DevTools:

A screenshot of the DevTools console showing the HTMLPortalElement

Developers can use the embed and activation APIs together immediately. Getting the page content ready for a portal on load and activating it after an animation is all that is required. Try it live at uskay-portals-demo.glitch.me:

  1. Enter a URL you want to preview.
  2. The page is embedded as a <portal> element.
  3. Click on the preview.
  4. The preview is activated after a transition animation.

The <portal> element can also be styled when it is embedded, allowing sites to show the incoming page framed as a card or preview before activation.

Key parts of the API

The spec, which is being discussed in the Web Incubation Community Group (WICG), revolves around three main interfaces:

  • The <portal> element: An HTML element with a src attribute, an activate function, and a postMessage interface for communication with the embedded page. The activate call accepts an optional argument used to pass data to the portal on activation.
  • The portalHost interface: Exposed on the window object, it tells the embedded document whether it is being displayed inside a <portal> and offers postMessage for communicating back to the host page.
  • The PortalActivateEvent: Fired when a portal is activated. Its adoptPredecessor() method retrieves the previous page as a <portal>, letting the newly activated page embed the outgoing document and create fully mutating, composed transitions between two pages.

Use cases beyond basics

The key scenarios at the WICG repository describe a broad set of patterns beyond navigation. For instance, a product listing page could pre-render the best-selling product’s detail page so it is ready for instant activation. The parent page can also receive a postMessage from its portal to reach exclusive settings on the detail page. The activated page can, in turn, render the departed listing page as a portal, reusing the same embedded pattern to support back navigation and user flows.

A differentiating factor from <iframe>-based previews is that Portals work across origins. Two independent sites that link to one another can use the API to set up seamless transitions between them. This cross-origin capability also applies inside SPAs.

Detailed developer documentation, the full API surface, and the community discussion are all available in the Portals spec repo. Feedback and feature requests are welcome through the same repository while the API is still in its experimental phase.