<div class="btn-container">
  <h1>Custom Built-In Element Examples</h1>
  <div class="basic-example example">
    <h2>Basic Examples</h2>
    <button is="my-button" id="regular-button" btntext="Example Button"></button>
    <button is="my-button" id="secondary-button" btntype="secondary" btntext="Secondary Button"></button>
    <button is="my-button" id="block-button" btntype="primary" btntext="Block Button" block></button>
  </div>

  <div class="props-and-attributes example">
    <h2>Change Attributes and Props</h2>
    <button is="my-button" id="attr-button" btntext="Attributes Button"></button>
    <button is="my-button" id="props-button" btntype="secondary" btntext="Properties Button"></button>
    <button is="my-button" id="bool-button" block btntext="Boolean Props"></button>
    <div class="input-wrapper">
      <label for="attr-input">Change attribute button text</label>
      <input id="attr-input" />
      <button id="attr-change-trigger">Change</button>
    </div>
    <div class="input-wrapper">
      <label for="properties-input">Change properties button text</label>
      <input id="properties-input" name="properties-input" />
      <button id="properties-change-trigger">Change</button>
    </div>
    <div class="input-wrapper">
      <label for="bool-checkbox">Toggle Block Button</label>
      <input id="bool-checkbox" type="checkbox" checked name="bool-checkbox" />
    </div>
  </div>
</div>
<script>
  const regularButton = document.getElementById("regular-button");
  const secondaryButton = document.getElementById("secondary-button");
  const blockButton = document.getElementById("block-button");
  for (const button of [regularButton, secondaryButton, blockButton]) {
    button.addEventListener("click", evt => {
      console.log("You clicked button with text: " + evt.currentTarget.btntext)
    })
  }
  const attrInput = document.getElementById("attr-input");
  const attrChangeButton = document.getElementById("attr-change-trigger");
  const attrButton = document.getElementById("attr-button");
  const propertiesInput = document.getElementById("properties-input");
  const propertiesChangeButton = document.getElementById("properties-change-trigger");
  const propertiesButton = document.getElementById("props-button");
  const boolButton = document.getElementById("bool-button");
  const boolCheckbox = document.getElementById("bool-checkbox");
  attrChangeButton.addEventListener("click", () => {
    attrButton.setAttribute("btntext", attrInput.value);
  });
  propertiesChangeButton.addEventListener("click", () => {
    propertiesButton.btntext = propertiesInput.value;
  });
  if (!boolCheckbox.checked) {
    boolButton.removeAttribute("block");
  }
  boolCheckbox.addEventListener("change", evt => {
    if (evt.currentTarget.checked) {
      boolButton.setAttribute("block", true);
    } else {
      boolButton.removeAttribute("block");
    }
  });
</script>
.btn-container,
.props-and-attributes {
  display: flex;
  flex-direction: column;
}

.btn-container {
  gap: 2rem;
}

.input-wrapper {
  width: 40%;
}

#slot-button::part(button) {
  background-color: red;
}

#bool-button {
  --button-default-color: green;
}

:root {
  --button-default-color: blue;
  --button-secondary-color: gray;
}

.my-button {
  max-width: 200px;
  border: 0;
  border-radius: 1rem;
  padding: 1rem;
  min-width: 100px;
  text-align: center;
}

.my-button--secondary {
  background-color: var(--button-secondary-color);
  color: white;
}

.my-button--primary {
  background-color: var(--button-default-color);
  color: white;
}

.my-button.my-button--block {
  max-width: 100%;
  width: 100%;
  display: block;
}
// A class defining a custom my-button element.
// Here we explicitly extend HTMLButtonElement
class MyButton extends HTMLButtonElement {
    #btnType = "primary"
    #btnText = ""
    #block = false

    static observedAttributes = ["btntext", "btntype", "block"]

    constructor() {
        super();
    }

    // Called when an attribute changes.
    attributeChangedCallback(attr, oldVal, newVal) {
        if (attr === "btntext" && oldVal !== newVal) {
            this.#btnText = newVal;
        }

        if (attr === "btntype" && oldVal === newVal) {
            this.#btnType = newVal;
        }

        if (attr === "block") {
            if (newVal === null) {
                this.#block = false
                this.classList.remove("my-button--block");
            } else {
                this.#block = true
                this.classList.add("my-button--block");
            }
        }
        this.render();
    }

    // When the component is attached to the DOM.
    connectedCallback() {
        this.#btnType = this.getAttribute("btntype") || this.#btnType; // Keep primary default
        this.#btnText = this.getAttribute("btntext");
        this.#block = this.hasAttribute("block");
        this.render();
    }

    // Getters and setters.
    // Allows getting/setting properties on the element using element.{property}
    // Similar to how you might grab the value off an input using input.value.
    set btntype(value) {
        this.#btnType = value;
        this.render();
    }

    get btntype() {
        return this.#btnType;
    }

    set btntext(value) {
        this.#btnText = value;
        this.render();
    }

    get btntext() {
        return this.#btnText;
    }

    // Method for generating a class name based on the btntype attribute.
    #getBtnTypeClass() {
        if (this.#btnType === "secondary") {
            return "my-button--secondary";
        }

        return "my-button--primary";
    }

    // Not a lifecycle hook!
    // Custom helper method for rendering the component.
    render() {
        const buttonClasses = ['my-button', this.#getBtnTypeClass(), (this.#block ? 'my-button--block' : '')].filter(clazz => !!clazz);
        this.textContent = this.#btnText;

        this.classList.add(...buttonClasses);
    }
}

// Defined slightly differently
// Notice we explicitly extend button here.
customElements.define("my-button", MyButton, { extends: "button" }); // Register the element with the registry.

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.