<div id="app"></div>
img-figure {
  display: block;
  img {
    border: 1px solid red;
  }
}
//Custom Element with shadow DOM
class ImgFigure extends HTMLElement {
  connectedCallback() {
    this.dom = this.attachShadow({mode: 'open'});
    this.src = this.getAttribute("src") || null;
    this.caption = this.getAttribute("caption") || "";
    this.alt = this.getAttribute("alt") || null;
    this.render();
  }

  render() {
    this.dom.innerHTML = this.template({
      src: this.src,
      alt: this.alt,
      caption: this.caption
    });
  }
  
  template(state) { 
    return `<style> 
    :host {
      display: inline-block; margin: 5px; padding: 5px;
      border: 1px solid #ccc; border-radius: 5px; }
    figure { margin: 0; }
    figure img {
      max-width: 100%; border: 1px solid #aaa; 
      border-radius: 5px; box-sizing: border-box; } 
    </style>
    <figure>
      <img
        src="${state.src}"
        alt="${state.alt || state.caption}">
      <figcaption>${state.caption}</figcaption>
    </figure>
    `;
  }
}

customElements.define("img-figure", ImgFigure);

// Some code to make above element work.
const maxWidth = 400;

const data = [{
  src: "//res.cloudinary.com/time2hack/image/upload/v1508699480/javascript-template-literals.png",
  alt: "JavaScript Template Literals",
  caption: "Benefits of JavaScript Template Literals and Tagged Templates"
}];
const elSrc = data => {
  return `<img-figure
  style="max-width: ${data.maxWidth || maxWidth}px"
  src="${data.src}"
  alt="${data.alt}"
  caption="${data.caption}"
></img-figure>`;
}

const add = data => {
  const d = document.createElement('div');
  d.innerHTML = elSrc(data);
  document.querySelector("#app").appendChild(d);
}
//attach to the DOM
add(data[0]);

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.css

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.19/webcomponents-hi-sd-ce.js