Polymer makes it simple to create web components, declaratively.

New web developers can simply add custom HTML elements on a web page with markdown. 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 Plunker 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 Polymer.Element, 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 (-).

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<script>
  // Define the class for a new element called custom-element
  class CustomElement extends Polymer.Element {
    static get is() { return "custom-element"; }
    constructor() {
        super();
        this.textContent = "I'm a custom-element.";
      }
  }
  // Register the new element with the browser
  customElements.define(CustomElement.is, CustomElement);
</script>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="custom-element.html">
  </head>
  <body>
    <custom-element></custom-element>
  </body>
</html>

Try it out in Plunker:

  • 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.

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<dom-module id="dom-element">

  <template>
    <p>I'm a DOM element. This is my shadow DOM!</p>
  </template>

  <script>
    class DomElement extends Polymer.Element {
      static get is() { return "dom-element"; }
    }
    customElements.define(DomElement.is, DomElement);
  </script>

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="dom-element.html">
  </head>
  <body>
    <dom-element></dom-element>
  </body>
</html>

Try it out in Plunker:

  • 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.

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<dom-module id="picture-frame">

  <template>
    <!-- scoped CSS for this element -->
    <style>
      div {
        display: inline-block;
        background-color: #ccc;
        border-radius: 8px;
        padding: 4px;
      }
    </style>
    <div>
      <!-- any children are rendered here -->
      <slot></slot>
    </div>
  </template>

  <script>
    class PictureFrame extends Polymer.Element {
      static get is() { return "picture-frame"; }
    }
    customElements.define(PictureFrame.is, PictureFrame);
  </script>

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="picture-frame.html">
  </head>
  <body>
    <picture-frame>
      <img src="https://www.polymer-project.org/images/logos/p-logo-32.png">
    </picture-frame>
  </body>
</html>

Try it out in Plunker:

  • 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.

<!-- import polymer-element -->
<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<dom-module id="name-tag">
  <template>
    <!-- bind to the "owner" property -->
    This is <b>{{owner}}</b>'s name-tag element.
  </template>
  
  <script>
    class NameTag extends Polymer.Element {
      static get is() { return "name-tag"; }
      
      // set this element's owner property
      constructor() {
        super();
        this.owner = "Daniel";
      }
    }
    
    customElements.define(NameTag.is, NameTag);
  </script>
</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="name-tag.html">
  </head>
  <body>
    <name-tag></name-tag>
  </body>
</html>

Try it out in Plunker:

  • 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 {{propertyName}} to the element’s shadow DOM.

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.

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<dom-module id="configurable-name-tag">

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

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="configurable-name-tag.html">
  </head>
  <body>
    <!-- configure a property from markup by setting
         the corresponding attribute                 -->
    <configurable-name-tag owner="Scott"></configurable-name-tag>
  </body>
</html>

Try it out in Plunker:

  • 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}}").

This 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.

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">
<!-- import the iron-input element -->
<link rel="import"  href="https://polygit.org/components/iron-input/iron-input.html">

<dom-module id="editable-name-tag">

  <template>
    <!-- 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}}">
      <input is="iron-input" placeholder="Your name here...">
    </iron-input>
  </template>

  <script>
    class EditableNameTag extends Polymer.Element {
      static get is() { return "editable-name-tag"; }
      
      // configure the owner property
      static get properties() {
        return {
          owner: {
            type: String,
            value: 'Daniel'
          }
        };
      }
      
    }
    customElements.define(EditableNameTag.is, EditableNameTag);
  </script>

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="editable-name-tag.html">
  </head>
  <body>
    <editable-name-tag></editable-name-tag>
  </body>
</html>

Try it out in Plunker:

  • 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.

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.

<!-- import polymer-element -->
<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<!-- import template repeater --> 
<link rel="import"  href="https://polygit.org/components/polymer/lib/elements/dom-repeat.html">

<dom-module id="employee-list">
  <template>
    <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>
  </template>
  <script>
    class EmployeeList extends Polymer.Element {
      static get is() { return "employee-list"; }
      
      // set this element's employees property
      constructor() {
        super();
        this.employees = [
          {first: 'Bob', last: 'Li'},
          {first: 'Ayesha', last: 'Johnson'},
          {first: 'Fatma', last: 'Kumari'},
          {first: 'Tony', last: 'Morelli'}
        ]; 
      }
    }
  customElements.define(EmployeeList.is, EmployeeList);
  </script>

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="employee-list.html">
  </head>
  <body>
    <employee-list></employee-list>
  </body>
</html>

Try it out in Plunker:

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

Learn more: Template repeater

Now that you understand these fundamental Polymer concepts, you can build an app with App Toolbox or see a feature overview of the Polymer library.