Skip to content
Branch: master
Find file History


This demo demonstrates how Portals can enable a seamless user experience between a website and third-party embedded content. Be creative and have fun!!


  • This demo consists of two websites (different origin):
    • PORTALOG, a blog service,
    • and TTT Archive, a podcast service.
  • TTT Archive is embedded in one of PORTALOG's articles using Portals.
  • Portals enables a seamless experience between the two websites.

This short explainer video is a great place to start from. hero img

Running the demo

1. Run the app

$ git clone
$ cd portals/demos/portal-embed-demo
$ npm install
$ npm run demo

Two local servers will start running.

$ npm run demo
📝 PORTALOG has launched: http://localhost:3000?portalport=3001
🎧 TTT Archive has launched: http://localhost:3001

2. Open a browser that supports Portals

As of May 2019, Chrome Canary is the only platform that supports Portals. You can try out Portals in Chrome Canary by flipping an experimental flag (chrome://flags/#enable-portals).

3. Access http://localhost:3000/?portalport=3001

...and you will see PORTALOG with TTT Archive embedded.

Note that HTMLPortalElement has not implemented the autoplay policies yet. The first time you access PORTALOG, audio might not play. In that case, try reloading PORTALOG, access TTT Archive directly and click on any buttons and go back, or disable the chrome://flags/#autoplay-policy while playing around with the demo.

4. Play around with it ;-)



This is to show how you can use Portals in cross origin situations. The demo runs a two local express servers for PORTALOG and TTT Archive to simulate a cross origin use case. By default, PORTALOG is available at http://localhost:3000/ and TTT Archive is available at http://localhost:3001/.

The basic structure of the demo is explained below.


Before getting into details... this could be the current experience w/o Portals withoutportals

  • You can embed third party contents with iframes.
  • But if you want to visit the content, a browser navigation starts and it needs to render all the content again which often leads to a slow page load experience.
  • If you are playing the audio, it just stops due to page navigation.

...But with having Portals 🚪🏃💨 withportals

You can embed third party content just like an iframe.

<!-- You can use it like iframes -->
<portal src=''></portal>


// From JavaScript
const portal = document.createElement('portal');
portal.src = '';

Demo code reference: creating a portal element

A page can detect if it is inside a portal and, if so, modify its UI accordingly.

// Detect whether this page is hosted in a portal
if (window.portalHost) {
  // Customize the UI when being embedded as a portal

Demo code reference: Check if window.portalHost is available and change the style

For now, portals do not respond to user input. If you want to interact with portals (like playing the audio in the demo), use postMessage.

// Send message to the portal element
const portal = document.querySelector('portal');
portal.postMessage({someKey: someValue}, ORIGIN);

// Receive message via window.portalHost
window.portalHost.addEventListener('message', evt => {
  const data =;
  // handle the event

Demo code reference: interacting with the audio player (sending messages and receiving messages)

When the user decides to navigate the the portal content i.e. click, it is a good opportunity to animate the portal and then call the activate function. User will be navigated to the portal content seamlessly (but the URL changes). The content continues running uninterrupted, and the audio even keeps playing after activation.

// do some fancy animations and after animations are complete, activate the portal.
const portal = document.querySelector('portal');

Demo code reference: animating the portal on click and activating the portal (note that you can optionally pass custom data to the portal)

Inside the portal content, you can listen to the portalactivate event to be notified when the page is activated. You can retrieve the previous page as a <portal> element by calling the adoptPredecessor function on the event. By leveraging the predecessor portal element, you can implement a seamless navigation experience when going back and forth between the two pages.

// Listen to the portalactivate event
window.addEventListener('portalactivate', evt => {
  // ... and creatively use the predecessor
  const portal = evt.adoptPredecessor();

Demo code reference: listening to portalactivate and reusing the predecessor

activate returns a promise that resolves when activation has completed.

// The activate function returns a Promise.
// When the promise resolves, it means that the portal has been activated.
// If this document was adopted by it, then window.portalHost will exist.
portal.activate().then(_ => {
  // Check if this document was adopted into a portal element.
  if (window.portalHost) {
    // You can start communicating with the portal element i.e. listen to messages
    window.portalHost.addEventListener('message', evt => {
      // handle the event

Demo code reference: sending messages to follow the writer of PORTALOG and handling the event in the article page


The code base is built for demo purpose only (non production ready code). It uses Web Components (Shadow DOM v1, Custom Elements v1), JS modules (import/export) and written in ES6 syntax. To make the demo simple, it is not transpiled to ES5 and does not include any polyfills. If you access the demo with a browser that does not support Portals, it will show a message as below with an iframe fallback (and the UI could break).


TTT Archive creatives

Some of the images and mp3 files used in the TTT Archive demo are modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.


You can’t perform that action at this time.