diff --git a/static/index.html b/static/index.html
new file mode 100644
index 0000000..5431954
--- /dev/null
+++ b/static/index.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8" />
+        <title>RGB Slider Tabs</title>
+        <link rel="stylesheet" href="styles.css" />
+    </head>
+    <body>
+        <div class="tabs"></div>
+        <div class="tab-content"></div>
+
+        <script type="module" src="main.js"></script>
+    </body>
+</html>
diff --git a/static/main.js b/static/main.js
new file mode 100644
index 0000000..6dce033
--- /dev/null
+++ b/static/main.js
@@ -0,0 +1,81 @@
+import "./rgb-slider.js";
+
+const ws = new WebSocket("ws://localhost:8000/ws");
+
+ws.onopen = () => {
+  console.log("WebSocket connection established");
+};
+
+ws.onclose = () => {
+  console.log("WebSocket connection closed");
+};
+
+ws.onerror = (error) => {
+  console.error("WebSocket error:", error);
+};
+
+// Number of sliders (tabs) you want to create
+const numTabs = 3;
+
+// Select the container for tabs and content
+const tabsContainer = document.querySelector(".tabs");
+const tabContentContainer = document.querySelector(".tab-content");
+
+// Create tabs dynamically
+for (let i = 1; i <= numTabs; i++) {
+  // Create the tab button
+  const tabButton = document.createElement("button");
+  tabButton.classList.add("tab");
+  tabButton.id = `tab${i}`;
+  tabButton.textContent = `Tab ${i}`;
+
+  // Add the tab button to the container
+  tabsContainer.appendChild(tabButton);
+
+  // Create the corresponding tab content (RGB slider)
+  const tabContent = document.createElement("div");
+  tabContent.classList.add("tab-pane");
+  tabContent.id = `content${i}`;
+  const slider = document.createElement("rgb-slider");
+  slider.id = i;
+  tabContent.appendChild(slider);
+
+  // Add the tab content to the container
+  tabContentContainer.appendChild(tabContent);
+
+  // Listen for color change on each RGB slider
+  slider.addEventListener("color-change", (e) => {
+    const { r, g, b } = e.detail;
+    console.log(`Color changed in tab ${i}:`, e.detail);
+    // Send RGB data to WebSocket server
+    if (ws.readyState === WebSocket.OPEN) {
+      const colorData = { r, g, b };
+      ws.send(JSON.stringify(colorData));
+    }
+  });
+}
+
+// Function to switch tabs
+function switchTab(tabId) {
+  const tabs = document.querySelectorAll(".tab");
+  const tabContents = document.querySelectorAll(".tab-pane");
+
+  tabs.forEach((tab) => tab.classList.remove("active"));
+  tabContents.forEach((content) => content.classList.remove("active"));
+
+  // Activate the clicked tab and corresponding content
+  document.getElementById(tabId).classList.add("active");
+  document
+    .getElementById("content" + tabId.replace("tab", ""))
+    .classList.add("active");
+}
+
+// Add event listeners to tabs
+tabsContainer.addEventListener("click", (e) => {
+  if (e.target.classList.contains("tab")) {
+    switchTab(e.target.id);
+  }
+});
+
+// Initially set the first tab as active
+switchTab("tab1");
diff --git a/static/rgb-slider.js b/static/rgb-slider.js
new file mode 100644
index 0000000..0c986df
--- /dev/null
+++ b/static/rgb-slider.js
@@ -0,0 +1,195 @@
+// rgb-slider.js
+
+export class RGBSlider extends HTMLElement {
+  constructor() {
+    super();
+    const shadow = this.attachShadow({ mode: "open" });
+
+    shadow.innerHTML = `
+      <style>
+        .container {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          padding: 1em;
+          border: 1px solid #ccc;
+          border-radius: 8px;
+          width: 50%;
+
+          font-family: sans-serif;
+          box-sizing: border-box;
+        }
+
+        .preview {
+          width: 50%;
+          height: 60px;
+          border-radius: 6px;
+          border: 1px solid #000;
+          background-color: rgb(0, 0, 0);
+          margin-bottom: 1em;
+        }
+
+        .sliders {
+          display: flex;
+          gap: 50px;
+          justify-content: center;
+          margin-bottom: 1em;
+        }
+
+        .slider-group {
+          display: flex;
+          flex-direction: column-reverse;
+          align-items: center;
+        }
+
+        .slider-group input[type="range"] {
+          writing-mode: vertical-lr;
+          direction: rtl;
+
+          width: 10px;
+          height: 120px;
+        }
+
+        .slider-group label {
+          margin-top: 8px;
+          font-size: 0.8em;
+        }
+
+        .rgb-inputs {
+          display: flex;
+          gap: 8px;
+        }
+
+        .rgb-inputs input {
+          width: 6ch;
+          padding: 2px;
+          font-family: monospace;
+          text-align: right;
+        }
+
+        .rgb-inputs label {
+          font-size: 0.8em;
+          text-align: center;
+        }
+
+        .rgb-input-group {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+        }
+
+        /* Mobile styles */
+        @media (max-width: 600px) {
+          .preview {
+            height: 80px;
+          }
+
+          .slider-group input[type="range"] {
+            height: 180px;
+            width: 14px;
+          }
+
+          .rgb-inputs input {
+            font-size: 1em;
+            padding: 4px;
+            width: 7ch;
+          }
+
+          .slider-group label,
+          .rgb-inputs label {
+            font-size: 1em;
+          }
+
+          .container {
+            padding: 1.5em;
+          }
+        }
+      </style>
+
+
+      <div class="container">
+        <div class="preview" id="preview"></div>
+
+        <div class="sliders">
+          <div class="slider-group">
+            <input type="range" min="0" max="255" value="0" id="r">
+            <label>R</label>
+          </div>
+          <div class="slider-group">
+            <input type="range" min="0" max="255" value="0" id="g">
+            <label>G</label>
+          </div>
+          <div class="slider-group">
+            <input type="range" min="0" max="255" value="0" id="b">
+            <label>B</label>
+          </div>
+        </div>
+
+        <div class="rgb-inputs">
+          <div class="rgb-input-group">
+            <label for="rInput">R</label>
+            <input type="number" min="0" max="255" id="rInput" value="0">
+          </div>
+          <div class="rgb-input-group">
+            <label for="gInput">G</label>
+            <input type="number" min="0" max="255" id="gInput" value="0">
+          </div>
+          <div class="rgb-input-group">
+            <label for="bInput">B</label>
+            <input type="number" min="0" max="255" id="bInput" value="0">
+          </div>
+        </div>
+      </div>
+    `;
+
+    const get = (id) => shadow.querySelector(id);
+    this.r = get("#r");
+    this.g = get("#g");
+    this.b = get("#b");
+    this.rInput = get("#rInput");
+    this.gInput = get("#gInput");
+    this.bInput = get("#bInput");
+    this.preview = get("#preview");
+
+    const updateColor = (r, g, b) => {
+      this.preview.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
+      this.rInput.value = r;
+      this.gInput.value = g;
+      this.bInput.value = b;
+      this.dispatchEvent(
+        new CustomEvent("color-change", {
+          detail: { r, g, b },
+          bubbles: true,
+          composed: true,
+        }),
+      );
+    };
+
+    const syncFromSliders = () => {
+      const r = +this.r.value;
+      const g = +this.g.value;
+      const b = +this.b.value;
+      updateColor(r, g, b);
+    };
+
+    const syncFromInputs = () => {
+      const r = Math.min(255, Math.max(0, +this.rInput.value));
+      const g = Math.min(255, Math.max(0, +this.gInput.value));
+      const b = Math.min(255, Math.max(0, +this.bInput.value));
+      this.r.value = r;
+      this.g.value = g;
+      this.b.value = b;
+      updateColor(r, g, b);
+    };
+
+    this.r.addEventListener("input", syncFromSliders);
+    this.g.addEventListener("input", syncFromSliders);
+    this.b.addEventListener("input", syncFromSliders);
+
+    this.rInput.addEventListener("change", syncFromInputs);
+    this.gInput.addEventListener("change", syncFromInputs);
+    this.bInput.addEventListener("change", syncFromInputs);
+  }
+}
+
+customElements.define("rgb-slider", RGBSlider);
diff --git a/static/styles.css b/static/styles.css
new file mode 100644
index 0000000..e3ecf1c
--- /dev/null
+++ b/static/styles.css
@@ -0,0 +1,37 @@
+/* General tab styles */
+.tabs {
+    display: flex;
+    justify-content: center;
+    margin-bottom: 20px;
+}
+
+.tab {
+    padding: 10px 20px;
+    margin: 0 10px;
+    cursor: pointer;
+    background-color: #f1f1f1;
+    border: 1px solid #ccc;
+    border-radius: 4px;
+    transition: background-color 0.3s ease;
+}
+
+.tab:hover {
+    background-color: #ddd;
+}
+
+.tab.active {
+    background-color: #ccc;
+}
+
+.tab-content {
+    display: flex;
+    justify-content: center;
+}
+
+.tab-pane {
+    display: none;
+}
+
+.tab-pane.active {
+    display: block;
+}