X-Tag is a lightweight, power-packed Web Components library that gets you there fast, and stays in its own lane.



What is X-Tag?

Originally developed at Mozilla, now supported by devs at Microsoft, X-Tag is an open source JavaScript library that wraps the W3C standard Web Components family of APIs to provide a compact, feature-rich interface for component development. While X-Tag makes it easy to leverage all the Web Components APIs (Custom Elements, Shadow DOM, Templates, and HTML Imports), it only requires Custom Elements API support to operate. In the absence of the native Custom Elements APIs, X-Tag uses the same polyfill Google's Polymer framework relies on. You can view our package options in the Builds section



xtag.create('x-clock', class extends XTagElement {
  connectedCallback () {
    this.start();
  }
  start (){
    this.update();
    this._interval = setInterval(() => this.update(), 1000);
  }
  stop (){
    this._interval = clearInterval(this._data.interval);
  }
  update (){
    this.textContent = new Date().toLocaleTimeString();
  }
  'tap::event' (){
    if (this._interval) this.stop();
    else this.start();
  }
});

Browser Support

Edge
Chrome (desktop and mobile)
Firefox (desktop and mobile)
Safari (Mac and iOS)
Opera (desktop and mobile)
Android Browser (OS version 5+)

Install from NPM:

npm install x-tag


Download the build that works best for you:

X-Tag
+
Polyfills

X-Tag + Custom Elements polyfill
6k min/gzip

Just X-Tag

No Polyfills

Run it raw on native APIs
3k min/gzip

Getting Started

The most important method in the X-Tag library is xtag.create(). The create function on the X-Tag object is what you'll use to create new custom element definitions, which can include things like lifecycle callbacks, attribute-linked getters/setters (accessors), and event listeners. Here's what defining a simple custom element looks like with X-Tag:


const Frank = xtag.create('x-frankenstein', class extends XTagElement {
  constructor (){
    alert("It's moving...it's alive!");
  }
});

Notice the class definition extends the class XTagElement. If you are not extending from a previously defined Custom Element, you must always extend from the XTagElement class for X-Tag to properly construct your Custom Element definition. The tag name argument (and subsequent automatic element registration) is optional - here's what it looks like without it, and how to register your element with a tag name separately:


const Frank = xtag.create(class extends XTagElement {
  constructor (){
    alert("Muhahaha!");
  }
});

xtag.register('x-frankenstein', Frank);

A Word on Extensions and Pseudos

In the next sections you're going to hear about a few features that are delivered via two different types of modular extensibility systems the library offers for imbuing your elements with advanced functionality. These two systems are called extensions and pseudos. Both are different in their intent, scope of effect, range of capabilities. You will use them frequently in your Custom Element class definition blocks, here's what they look like:


xtag.create('x-foo', class extends XTagElement {
  'alert::foo' (){

  }
  'count:bar' (){

  }
  'send::attr:bar:zaz' (){
    
  }
});

Notice how the alert method has a ::foo attached to it - this is an extension. Extensions are little modules you can created that are called into service by tagging a property with :: + the name of the extension. Extensions are a sizable API of their own that let's you manipulate far more than just the property they are tagged to. You can only use one extension per property.

The other API for adding custom functionality to components is called pseudos, which are seen above in the example property count:bar. When a pseudo is tagged to a property, the pseudo's own function wraps the target function so that the pseudo is executed first, then the target, which receives its return value. You can chain pseudos, and they are compiled from left to right so that they left most pseudo function wrapper fires first, passing each function's return value down to the next, until finally the original target function is executed. You can see what this looks like in the above example property send::foo:bar:zaz.

There are separate guides for extensions and pseudos that talk about how to create and use your own, but for now just be aware that they are one of the primary means the X-Tag library delivers its own features.


Content is King

Many of the components you create will require child elements for structure and presentation. X-Tag includes a content templating extension that makes this quick and easy. Simply add a ::template function to your component definition and return a string or ES6 Template String. In the example below, notice the true parameter being passed to the extension. This tells the template extension to render the content immediately when an instance is created. If you don't want a template to render automatically like this, don't pass anything to it.


xtag.create('x-frankenstein', class extends XTagElement {
  name (){ return 'Frankenstein'; }
  '::template(true)' (){
    return `<h2>I am ${this.name()}</h2>
            <span>I was created by a mad scientist</span>`
  }
});

Manual Rendering of Templates

If you elect not to have your template automatically rendered when your component instances are created, you'll need to manually trigger render.


const Frank = xtag.create('x-frankenstein', class extends XTagElement {
  name (){ return 'Frankenstein'; }
  '::template' (){
    return `<h2>I am ${this.name()}</h2>
            <span>I was created by a mad scientist</span>`
  }
});

const FrankNode = new Frank();
FrankNode.render();

Multiple Templates

The template extension can be used multiple times within the same class definition block, which allows you to specify different templates for use in the same component. The templates are cached and reference based on the property name you put in front of the ::template extension token. To render your templates, simply pass the name of the template you want to use to the render function, as in the following example:


const Frank = xtag.create('x-frankenstein', class extends XTagElement {
  'foo::template(true)' (){
    return `<h2>My name is Frankenstein</h2>
            <span>I work for a mad scientist</span>`
  }
  'bar::template' (){
    return `<h2>My friends call me Frank</h2>
            <span>I work for a mad scientist</span>`
  }
});

const FrankNode = new Frank();
// Because the foo template indicated it was to be automatically rendered, its content is already present in your element instance.
FrankNode.render('bar');
// Calling the render function with the bar template name swaps out the current foo template content with the bar content.

Say Hello to Custom Attribute Accessors

Accessors are custom attributes linking to a corresponding pair of setters/getters, and X-Tag has a built-in extension that makes adding accessors to your custom element definition stupid simple. To use this extension, add an ::attr modifier to one of your class definition properties. This tells X-Tag to wire up a getter/setter to an HTML attribute of the same name. When attributes are linked to a getter/setter their gets, sets, and state changes will be synced without having to write any additional code.

You can also tell the accessor extension that type of accessor you are creating, and it will help by sanitizing values and modifying the gets/sets to match the behavior of the type you declare. I


xtag.create('x-foo', class extends XTagElement {
  set 'maxVolume::attr' (value){
    // X-Tag automatically maps camel cased getter/setter names to their
    // dashed attribute equivalents. In this example, the `maxVolume` 
    // getter/setter pair maps to the `max-volume` attribute.
  }
});

xtag.create('x-bar', class extends XTagElement {
  '::template(true)' (){ return '<input />' }
  set 'disabled::attr(boolean)' (value){
    // The ::attr extended property links node.disabled to gets/sets of 
    // the disabled="" attribute. Because it is declared as a boolean, 
    // all values passed into its getter/setter will be a Boolean.
    this.firstElementChild.disabled = value;
  }
});

The DL on Events

X-Tag provides a powerful event system you'll use often in developing your components. We'll cover the basics here, and leave the more advanced features for a special tutorial dedicated to the topic.

To add event listeners to your component (either native or custom events), you'll be using the event extension. To do so, add a property to your Custom Element definition that matches the name of the event you want to listen for, plus the ::event extension flag. When your class definition is processed by X-Tag's create method, the event extension will remove the property, add a listener using the supplied property name with the function you supplied as the handler.


xtag.register('x-foo', {
  'click::event': function(){
    // attaches a click listener that calls this function when
    // the user clicks an element inside the custom element
  },
  'focus::event': function(){
    // attaches a focus listener that calls this function when
    // something inside your custom element is focused.
  }
});

Gettin' Jedi with Pseudos

One advanced feature of X-Tag you should be aware of right off the bat is the delegate pseudo. X-Tag features a function modifier system called pseudos which allows you to seamlessly wrap functions anywhere in your custom element defintion object (lifecycle callbacks, methods, accessors, and events) to extend their functionality. The delegate pseudo enables you to quickly add event delegation (filtering of event targets based on CSS expressions) to any event function you add to your component. Here's an example:


xtag.register('x-foo', {
  content: '<input/>',
  events: {
    'tap:delegate(input)': function(){
      // Perform an action only when the user taps on an
      // <input/> element within your component.
    }
  }
});

Create your own Pseudo:

Once you create a pseudo and add it to the main xtag.pseudos object, as show below, you can then use it on any key of your custom element definition objects that has a function as its value.


xtag.pseudos.foo = {
  onParse: function(ctor, property, args, fn){
    /*
      Parse is called when the pseudos specified in a declaration are being 
      compiled and wrapped around the function they are being applied to.


      ctor: the Custom Element Class definition
      property: property the pseudo is being applied to (ex: ping:foo, property = ping)
      args: arguments from the pseudo definition (ex: prop:foo(1, 2), args = [1, 2])
      fn: the function the pseudo is wrapping in the pseud chain
    */
  },
  onInvoke: function(node, obj){
    /*
      Invoke is called any time the pseudo-wrapped function is called. This is where 
      you perform whatever action you desire on the node or object the pseudo targets.

      node: the element
      obj: object composed of these properties: { fn: fn, args: args, detail: detail } 
    */
  }
};

Inheritance

X-Tag uses the standard Custom Elements mechanism for inheritance: ES6 class extension. The return value of the create method is an X-Tag compiled ES6 class, which can be itself extended either via generic ES6 class declaration, or by again passing it into create, if you wanted to add additional features to your new extended class using X-Tag APIs.


const Foo = xtag.create('x-foo', class extends XTagElement {
  set 'foo::attr' (){ /* Handle foo attribute change */ }
});

Bar extends Foo {
  bar (){ }
}

const Zaz = xtag.create('x-zaz', class extends Bar {
  'click::event' (){ /* do something when clicked */ }
});

API Reference

xtag.create(TAG_NAME, DEFINITION)

Create and (optionally) register a Custom Element definition for use as a new tag in HTML markup or via DOM APIS.

Argument Type Description
TAG_NAME
(optional)
String The tag name you want to use when using your Custom Element (must contain a dash). When this argument is passed, the Custom Element definition is automatically added to the document's Custom Element registry.
DEFINITION Class The class definition block for your Custom Element

const Frank = xtag.create('x-frankenstein', class extends XTagElement {
  constructor (){
    alert("It's moving...it's alive!");
  }
});

xtag.addEvent(ELEMENT, TYPE, HANDLER, CAPTURE)

Add a native, custom, or X-Tag library event listener to an element. The TYPE field also accepts pseudo chains for even more powerful event handling.

Argument Type Description
ELEMENT DOM element reference The element you would like to attach an event listener to.
TYPE String The event name, with any event pseudos you may want to chain.
HANDLER Function The event handler you want called when the event occurs.
CAPTURE Function Boolean argument you can pass if you want the event to use capturing vs bubbling.

xtag.addEvent(myElement, 'tap:delegate(img)', function(event){
  alert('An image element within myElement was just tapped');
});

xtag.addEvents(ELEMENT, OBJECT)

Add multiple native, custom, or X-Tag library event listeners to an element using one function. Pseudo chains are avaliable for use in your event object keys.

Argument Type Description
ELEMENT DOM element reference The element you would like to attach the event listeners to.
OBJECT Object An object composed of multiple events - keys are event names, and values are the event handlers.

xtag.addEvents(myElement, {
  'tap': function(event){
    alert('myElement was just tapped');
  },
  'keypress:keypass(13)': function(event){
    alert('The enter key was pressed, all other keys are blocked');
  }
});

xtag.fireEvent(ELEMENT, NAME, OPTIONS)

Add a native, custom, or X-Tag library event listener to an element. The TYPE field also accepts pseudo chains for even more powerful event handling.

Argument Type Description
ELEMENT DOM element reference The element you would like to attach an event listener to.
NAME String The event name you wish to fire on the target element.
OPTIONS Object The event options you want to use in firing your event. These include:
  • detail allows passage of custom data via the detail property of the event
  • bubbles specify whether the event bubbles - defaults to true
  • cancelable specify whether the event is cancelable - defaults to true

xtag.fireEvent(myElement, 'show', {
  detail: {
    x: 123,
    y: 456
  }
});