diff --git a/packages/calcite-components/src/demos/_assets/demo-theme.ts b/packages/calcite-components/src/demos/_assets/demo-theme.ts
new file mode 100644
index 00000000000..8fec8edb784
--- /dev/null
+++ b/packages/calcite-components/src/demos/_assets/demo-theme.ts
@@ -0,0 +1,112 @@
+/*
+ * EXAMPLE USAGE:
+ *
+ * Button
+ *
+ */
+
+const defaultTheme = {
+ background: "blue",
+ cornerRadius: "42px",
+ shadowColor: "black",
+ shadowValues: "0 1px 2px 0 1",
+ space: "24px",
+ textColor: "green",
+ zIndex: "9999",
+};
+
+export class DemoTheme extends HTMLElement {
+ _slot: HTMLSlotElement;
+
+ _el: HTMLElement;
+
+ _theme: Record = defaultTheme;
+
+ static observedAttributes = ["tokens"];
+
+ constructor() {
+ super();
+ const shadow = this.attachShadow({ mode: "open" });
+ const slot = document.createElement("slot");
+ shadow.append(slot);
+ this._slot = slot;
+ if (this._slot.assignedNodes().length === 1 && this._slot.assignedNodes()[0].nodeName.includes("calcite")) {
+ this._el = this._slot.assignedNodes()[0] as HTMLElement;
+ }
+ }
+
+ attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
+ if (newValue !== oldValue && name === "tokens") {
+ this.updateTheme(newValue);
+ }
+ }
+
+ updateTheme(newValue: string): void {
+ if (typeof newValue === "string") {
+ const textIconColorRegex = new RegExp(/(text|icon)-color/);
+ const backgroundBorderRegex = new RegExp(/(background|border)-color/);
+ const cornerRegex = new RegExp(/corner-radius/);
+ const zIndexRegex = new RegExp(/z-index/);
+ const spaceRegex = new RegExp(/space/);
+ const shadowRegex = new RegExp(/shadow(-color)*/);
+
+ const theme = {};
+
+ let tokensList;
+
+ try {
+ tokensList = JSON.parse(newValue);
+ } catch (error) {
+ tokensList = newValue.split(",").map((t) => t.trim());
+ }
+
+ if (Array.isArray(tokensList)) {
+ tokensList.forEach((token) => {
+ let value = "";
+ if (textIconColorRegex.test(token)) {
+ value = this._theme.textColor;
+ } else if (backgroundBorderRegex.test(token)) {
+ value = this._theme.background;
+ } else if (shadowRegex.test(token)) {
+ value = token.includes("color")
+ ? `${this._theme.shadowColor}`
+ : `${this._theme.shadowValues} ${this._theme.shadowColor}`;
+ } else if (cornerRegex.test(token)) {
+ value = `${this._theme.cornerRadius}`;
+ } else if (zIndexRegex.test(token)) {
+ value = `${this._theme.zIndex}`;
+ } else if (spaceRegex.test(token)) {
+ value = `${this._theme.space}`;
+ }
+ theme[token] = value;
+ });
+ }
+
+ const stringifiedTheme = Object.entries(theme)
+ .map(([key, value]) => {
+ return `${key}: ${value};`;
+ })
+ .join(" ");
+
+ if (this._el) {
+ this._el.style.cssText = stringifiedTheme;
+ } else {
+ this.setAttribute("style", stringifiedTheme);
+ }
+ }
+ }
+}
+
+customElements.define("demo-theme", DemoTheme);
diff --git a/packages/calcite-components/src/demos/button.html b/packages/calcite-components/src/demos/button.html
index 0e141ff1803..fcef34b7885 100644
--- a/packages/calcite-components/src/demos/button.html
+++ b/packages/calcite-components/src/demos/button.html
@@ -93,6 +93,7 @@
+
@@ -2564,6 +2565,37 @@
+
+
+