Polymer makes it simple to create web components, declaratively.

New web developers can simply add custom HTML elements on a web page with markup. It’s just like using the HTML tags you’re already familiar with:

<h1>A heading!</h1>
<fancy-thing>A fancy thing!</fancy-thing>

Experienced web developers can use Polymer's special features to reduce boilerplate and make it even easier to build complex, interactive elements. In this tour, you'll learn how to:

  • Register elements
  • Use lifecycle callbacks
  • Observe properties
  • Create shadow DOM with templates
  • Use data binding

In this section you can tour the Polymer library, without installing anything. Click the Edit on StackBlitz button to open any of the samples in an interactive sandbox.

Tap the buttons following each feature to learn more.

To register a new element, create an ES6 class that extends PolymerElement, then call the customElements.define method, which registers a new element with the browser. Registering an element associates an element name with a class, so you can add properties and methods to your custom element. The custom element's name must start with an ASCII letter and contain a dash (-).

custom-element.js
import {PolymerElement} from '@polymer/polymer/polymer-element.js';

// Define the class for a new element called custom-element
class CustomElement extends PolymerElement {
  constructor() {
    super();
    this.textContent = 'I\'m a custom-element.';
  }
}
// Register the new element with the browser
customElements.define('custom-element', CustomElement);

/*
  If you’re familiar with your browser’s developer tools, try printing the
  custom element’s `tagName` property to the console.
  Hint: add `console.log(this.tagName);` to the constructor method!
*/

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <custom-element></custom-element>
  </body>
</html>

Try it out in StackBlitz:

  • Try modifying the contents of this.textContent.
  • If you’re familiar with your browser’s developer tools, try printing the custom element’s tagName property to the console. Hint: add console.log(this.tagName); to the constructor method!

This sample uses a lifecycle callback to add contents to the <custom-element> when it's initialized. When a custom element finishes its initialization, the ready lifecycle callback is called. You can use the ready callback for one-time initialization work after the element is created.

Learn more: element registration

Learn more: lifecycle callbacks

Many elements include some internal DOM nodes to implement the element's UI and behavior. You can use Polymer's DOM templating to create a shadow DOM tree for your element.

dom-element.js
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'

// Define the class for a new element called custom-element
class DomElement extends PolymerElement {

  static get template () {
    return html`
      <p>I'm a DOM element. This is my shadow DOM!</p>

      <!-- TODO: Try adding some other html elements inside the
           template. For example, add <h1>A heading!</h1> or
           <a href="stuff.html">A link!</a>
      -->
    `;
  }
}
// Register the new element with the browser
customElements.define('dom-element', DomElement);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <dom-element></dom-element>
  </body>
</html>

Try it out in StackBlitz:

  • Try adding some other html elements inside the block. For example, add <h1>A heading!</h1> or <a href="stuff.html">A link!</a>

Shadow DOM is encapsulated inside the element.

Learn more: DOM templating

Shadow DOM lets you control composition. The element's children can be distributed so they render as if they were inserted into the shadow DOM tree.

This example creates a simple tag that decorates an image by wrapping it with a styled <div> tag.

picture-frame.js
import {PolymerElement, html} from "@polymer/polymer/polymer-element.js"

class PictureFrame extends PolymerElement {
  static get template() {
    return html`
    <!-- scoped CSS for this element -->
    <style>
      div {
        display: inline-block;
        background-color: #ccc;
        border-radius: 8px;
        padding: 4px;
      }
    </style>
    <!--
    TODO: Try adding other HTML elements to the DOM template
    to see how they are positioned relative to the distributed
    child nodes.
    -->
    <div>
      <!-- any children are rendered here -->
      <slot></slot>
    </div>
    `;
  }
}
customElements.define('picture-frame', PictureFrame);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <!--
    TODO: Try adding <div> elements in index.html to observe
    how they are unaffected by the encapsulated styles applied
    to the divs in picture-frame.html.
    -->
    <picture-frame>
      <img src="https://www.polymer-project.org/images/logos/p-logo-32.png">
    </picture-frame>
  </body>
</html>

Try it out in StackBlitz:

  • Try adding a <div> to index.html; is it affected by the styles in <picture-frame>'s shadow DOM?
  • Try adding other HTML elements to the DOM template to see how they are positioned relative to the distributed child nodes.

Note: The CSS styles defined inside the <dom-module> are scoped to the element's shadow DOM. So the div rule here only affects <div> tags inside <picture-frame>.

Learn more: Composition & distribution

Of course, it's not enough to have static shadow DOM. You usually want to have your element update its shadow DOM dynamically.

Data binding is a great way to quickly propagate changes in your element and reduce boilerplate code. You can bind properties in your component using the "double-mustache" syntax ({{}}). The {{}} is replaced by the value of the property referenced between the brackets.

name-tag.js
import {PolymerElement, html} from "@polymer/polymer/polymer-element.js"

class NameTag extends PolymerElement {
  constructor() {
    super();

    /* set this element's owner property */
    this.owner = 'Daniel';
  }
  static get template() {
    return html`
      <!-- bind to the "owner" property -->
      This is <b>{{owner}}</b>'s name-tag element.
    `;
  }
}
customElements.define('name-tag', NameTag);
/* TODO:
  * - Try editing the value of the `owner` property.
  * - Try adding another property and binding it in
  *   your component. Hint: Add the following property
  *   definition to the constructor:
  *   `this.propertyName = "Property contents";`
  *   and add `{{propertyName}}` to the element’s shadow DOM.
  */

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <name-tag></name-tag>
  </body>
</html>

Try it out in StackBlitz:

  • Try editing the value of the owner property.
  • Try adding another property and binding it in your component. Hint: Add this.propertyName = 'Property contents'; to the constructor and add something like <p>{{propertyName}}</p> to the element’s template.

Learn more: data binding

Properties are an important part of an element's public API. Polymer declared properties support a number of common patterns for properties—setting default values, configuring properties from markup, observing property changes, and more.

The following example declares the owner property from the last example. It also shows configuring the owner property from markup in index.html.

configurable-name-tag.js
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'

class ConfigurableNameTag extends PolymerElement {
  static get properties () {
    return {
      // Configure owner property
      owner: {
        type: String,
          value: 'Daniel',
      }
    };
  }
  static get template () {
    return html`
      <!-- bind to the "owner" property -->
      This is <b>[[owner]]</b>'s name-tag element.
    `;
  }
}
customElements.define('configurable-name-tag', ConfigurableNameTag);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <!-- configure a property from markup by setting
         the corresponding attribute:
    -->
    <configurable-name-tag owner="Scott"></configurable-name-tag>
    <!--
      TODO: Try editing the initial value of `owner` in
      index.html. Observe how this sets the property directly
      from your HTML.
    -->
  </body>
</html>

Try it out in StackBlitz:

  • Try editing the initial value of owner in index.html. Observe how this sets the property directly from your HTML.

Learn more: declared properties

In addition to text content, you can bind to an element's properties (using property-name="[[binding]]"). Polymer properties can optionally support two-way binding, using curly braces (property-name="{{binding}}").

The following example uses two-way binding: binding the value of a custom input element (iron-input) to the element's owner property, so it's updated as the user types.

editable-name-tag.js
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
import '@polymer/iron-input/iron-input.js';

class EditableNameTag extends PolymerElement {
  static get properties () {
    return {
      owner: {
        type: String,
        value: 'Daniel'
      }
    };
  }
  static get template () {
    return html`
      <!-- bind to the 'owner' property -->
      <p>This is <b>[[owner]]</b>'s name-tag element.</p>

      <!-- iron-input exposes a two-way bindable input value -->
      <iron-input bind-value="{{owner}}">
        <!--
          TODO: Edit the placeholder text to see two-way data
          binding at work.
        -->
      <input is="iron-input" placeholder="Your name here...">
      </iron-input>
    `;
  }
}

customElements.define('editable-name-tag', EditableNameTag);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <editable-name-tag></editable-name-tag>
  </body>
</html>

Try it out in StackBlitz:

  • Edit the placeholder text to see two-way data binding at work.

Note: The <iron-input> element wraps a native <input> element and provides two-way data binding and input validation.

Learn more: Two-way binding

The template repeater (dom-repeat) is a specialized template that binds to an array. It creates one instance of the template's contents for each item in the array.

employee-list.js
//import the Polymer library
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'

//import the template repeater
import '@polymer/polymer/lib/elements/dom-repeat.js'

class EmployeeList extends PolymerElement {
  constructor() {
    super();
    /* TODO:
     * - Change the first and last names inside this.employees
     * - Add another employee by inserting another object
     *   into the array definition after Tony Mori:
     *   {first: 'Shawna', last: 'Williams'}
     *   Remember to make sure your commas are correct!
     */

    this.employees = [
      {first: 'Bob', last: 'Li'},
      {first: 'Ayesha', last: 'Johnson'},
      {first: 'Fatma', last: 'Kumari'},
      {first: 'Tony', last: 'Mori'}
    ];
  }
  static get template () {
    return html`
    <div> Employee list: </div>
    <p></p>
    <template is="dom-repeat" items="{{employees}}">
        <div>First name: <span>{{item.first}}</span></div>
        <div>Last name: <span>{{item.last}}</span></div>
        <p></p>
    </template>
    `;
  }
}
customElements.define('employee-list', EmployeeList);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <employee-list></employee-list>
  </body>
</html>

Try it out in StackBlitz:

  • Change the first and last names inside this.employees
  • Add another employee by inserting the following item into the array definition:
       {first: 'Shawna', last: 'Williams'}
    

Don't forget to make sure your commas are correct!

Learn more: Template repeater

Now that you understand these fundamental Polymer concepts: