Step 1: Include Header Assets
Add the necessary CSS and JavaScript libraries to your HTML document’s header. This step ensures the correct functioning of the color picker and other styling elements.
<link rel='stylesheet' href='https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.css'>
Step 2: Create HTML Structure
Set up the basic HTML structure for your color palette generator. This includes divs for the background, overlay, and interactive elements.
<div id="backgroundStrip" aria-hidden="true"></div> <div id="backgroundGrid" aria-hidden="true"></div> <div class="overlay" role="region" aria-label="Contrôles de génération de couleurs"> <div class="title">Color Palette Generator </div> <div class="subtitle">This tool generates beautiful color palettes from any base color. Use the Random button or pick a color with the selector, then explore different harmony schemes. <a id="link" href="https://en.wikipedia.org/wiki/Color_scheme" target="_blank" rel="noopener">Learn More</a></div> <div class="row" style="align-items:center"> <div class="input-wrap"> <input id="baseColor" type="text" value="#007bff" data-coloris aria-label="Couleur de base" /> </div> <button class="btn" id="randomize"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> <path d="M18 4l3 3l-3 3" /> <path d="M18 20l3 -3l-3 -3" /> <path d="M3 7h3a5 5 0 0 1 5 5a5 5 0 0 0 5 5h5" /> <path d="M21 7h-5a4.978 4.978 0 0 0 -3 1m-4 8a4.984 4.984 0 0 1 -3 1h-3" /> </svg> </button> <div style="margin-left:auto" class="controls-right"> <button class="btn" id="copyCss">Copy CSS</button> <button class="btn" id="copyHexes">Copy HEX</button> </div> </div> <div class="row"> <div class="tabs" id="tabs"> <button class="tab" data-filter="complementary">Complementary</button> <button class="tab active" data-filter="split">Split complementary</button> <button class="tab" data-filter="monochrome">Monochrome</button> <button class="tab" data-filter="analogous">Analogous</button> <button class="tab" data-filter="triadic">Triadic</button> <button class="tab" data-filter="quadratic">Quadratic</button> </div> </div> </div> <div id="toast" class="toast" role="status" aria-live="polite"></div>
Step 3: Add CSS Styling
Apply CSS styles to structure and style the elements. This step ensures a visually appealing and user-friendly interface.
@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); body { height: 100%; margin: 0; } body, button, input { font-family: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; } #link { color: #475569; } #backgroundStrip { position: fixed; inset: 0; display: grid; grid-template-columns: 1fr 1fr; z-index: -1; /* ensure it’s behind everything */ } #backgroundGrid { position: fixed; inset: 0; display: grid; grid-auto-rows: 1fr; grid-auto-columns: 1fr; grid-auto-flow: column; gap: 0; width: 100vw; height: 200vh; margin-top: -50vh; z-index: 0; } #backgroundGrid .bg-cell { display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 18px; box-sizing: border-box; gap: 10px; transform: rotate(18deg); transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .bg-cell .code { font-weight: 700; letter-spacing: 0.01em; font-size: 2rem; transform: rotate(-18deg); transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); } .bg-cell .var { font-size: 12px; opacity: 0.9; padding: 4px 8px; border-radius: 999px; background: rgba(255, 255, 255, 0.12); display: none; transform: rotate(-18deg); } .bg-cell button.copy { margin-top: 8px; padding: 8px 16px; border-radius: 8px; border: none; cursor: pointer; font-weight: 700; transform: rotate(-18deg); margin-left: 6%; mix-blend-mode: hard-light; } #randomize { padding: 16px 16px; } .overlay { font-family: "Inter"; position: fixed; left: 50%; top: 0; transform: translate(-50%, 0%); z-index: 20; background: rgba(255, 255, 255, 0.88); border-radius: 0px 0px 16px 16px; padding: 24px 24px 8px 24px; box-shadow: 0 12px 40px rgba(2, 6, 23, 0.28); backdrop-filter: blur(6px) saturate(120%); max-width: 720px; } .clr-field { flex: 1; font-weight: 900; } .clr-field button { border-radius: 4px; overflow: hidden; cursor: pointer; } .clr-alpha { display: none; } .title { font-size: 1.5em; font-weight: 800; margin-bottom: 0.5em; } .subtitle { font-size: 13px; color: #475569; margin-bottom: 14px; } .row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; } .input-wrap { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 10px; background: #fff; border: 1px solid rgb(230, 230, 230); height: 32px !important; border-radius: 8px; } .input-wrap input { border: none; background: transparent; outline: none; font-size: 16px; padding: 6px; } .btn { padding: 16px 24px; border-radius: 8px; border: 1px solid rgb(230, 230, 230); background: white; color: #191919; cursor: pointer; font-weight: 700; display: flex; align-items: center; gap: 8px; } .btn.ghost { background: transparent; } .tabs { display: flex; gap: 8px; flex-wrap: wrap; } .tab { padding: 8px 14px; border-radius: 8px; border: none; cursor: pointer; font-weight: 400; font-size: 14px; } .tab.active { background: #191919; color: white; } .controls-right { display: flex; gap: 8px; margin-left: auto; } .toast { position: fixed; right: 14px; bottom: 14px; background: #111827; color: white; padding: 10px 12px; border-radius: 8px; opacity: 0; transform: translateY(8px); transition: all 0.28s ease; z-index: 60; } .toast.show { opacity: 1; transform: translateY(0); } @media only screen and (max-width: 1220px) { .input-wrap { width: 70%; } .controls-right { margin-left: 0 !important; } .code { font-size: 1rem !important; } #backgroundGrid { height: 100vh; margin-top: 0vh; } #backgroundGrid .bg-cell, .bg-cell .code, .bg-cell button.copy { transform: rotate(0deg); } .bg-cell button.copy { margin-left: 0; } #backgroundGrid .bg-cell { justify-content: flex-end; } } @media only screen and (max-width: 600px) { .bg-cell .code { font-size: 0.7rem !important; } .bg-cell button.copy { font-size: 0.7rem; padding: 4px 8px; } #backgroundGrid .bg-cell { padding: 16px 0px; } }
Step 4: Implement JavaScript Logic
This is the core of your application; it handles color generation, scheme selection, CSS variable application, and clipboard functionality.
const clamp = (v, a, b) => Math.min(Math.max(v, a), b); const mod = (n, m) => ((n % m) + m) % m; function parseColor(input) { const s = String(input || "").trim(); let m = s.match(/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i); if (m) { let h = m[1]; if (h.length === 3) h = h .split("") .map((c) => c + c) .join(""); const num = parseInt(h, 16); return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 }; } m = s.match(/^rgba?\(([^)]+)\)$/i); if (m) { const parts = m[1] .split(/[,/ ]+/) .map((p) => p.trim()) .filter(Boolean); const [r, g, b] = parts; const px = (v) => v.endsWith("%") ? Math.round(parseFloat(v) * 2.55) : Math.round(parseFloat(v)); return { r: px(r), g: px(g), b: px(b) }; } m = s.match(/^hsla?\(([^)]+)\)$/i); if (m) { const parts = m[1] .split(/[,/ ]+/) .map((p) => p.trim()) .filter(Boolean); const [h, sPct, lPct] = parts; const hN = parseFloat(h); const sN = parseFloat(sPct) / (sPct.endsWith("%") ? 100 : 1); const lN = parseFloat(lPct) / (lPct.endsWith("%") ? 100 : 1); return hslToRgb(hN, sN, lN); } return { r: 26, g: 164, b: 255 }; } function rgbToHex({ r, g, b }) { const toHex = (v) => Math.round(v).toString(16).padStart(2, "0"); return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase(); } function rgbToHsl({ r, g, b }) { r /= 255; g /= 255; b /= 255; const max = Math.max(r, g, b), min = Math.min(r, g, b); let h = 0, s = 0, l = (max + min) / 2; if (max !== min) { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h *= 60; } return { h, s, l }; } function hslToRgb(h, s, l) { h = mod(h, 360) / 360; let r, g, b; if (s === 0) r = g = b = l; else { const hue2rgb = (p, q, t) => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1 / 3); } return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) }; } function hslToHex(h, s, l) { return rgbToHex(hslToRgb(h, s, l)); } function adjust(h, s, l, { dh = 0, ds = 0, dl = 0 } = {}) { return { h: mod(h + dh, 360), s: clamp(s + ds, 0, 1), l: clamp(l + dl, 0, 1) }; } function idealTextColor(hex) { const { r, g, b } = parseColor(hex); const yiq = (r * 299 + g * 587 + b * 114) / 1000; return yiq >= 128 ? "#0f172a" : "#FFFFFF"; } function generateSchemes(baseHex) { const baseRgb = parseColor(baseHex); const { h, s, l } = rgbToHsl(baseRgb); const complementary = [adjust(h, s, l, { dh: 180 })]; const split = [ adjust(h, s, l, { dh: 180 - 30 }), adjust(h, s, l, { dh: 180 + 30 }) ]; const analogous = [-60, -30, 30, 60].map((dh) => adjust(h, s, l, { dh })); const triadic = [-120, 120].map((dh) => adjust(h, s, l, { dh })); const quadratic = [90, 180, 270].map((dh) => adjust(h, s, l, { dh })); const monoSteps = [-0.22, -0.12, 0.12, 0.22]; const monochrome = monoSteps.map((dl) => adjust(h, s, l, { dl, ds: dl > 0 ? -0.05 : 0.05 }) ); const toObj = (obj, i, cat) => { const hex = hslToHex(obj.h, obj.s, obj.l); return { hex, hsl: obj, category: cat, varName: `--color-${cat}-${i + 1}` }; }; const pack = (arr, cat) => arr.map((o, i) => toObj(o, i, cat)); return { base: { hex: hslToHex(h, s, l), h, s, l }, complementary: pack(complementary, "complementary"), split: pack(split, "split"), monochrome: pack(monochrome, "monochrome"), analogous: pack(analogous, "analogous"), triadic: pack(triadic, "triadic"), quadratic: pack(quadratic, "quadratic") }; } function uniqueAll(schemes, limit = 12) { const all = [ ...schemes.complementary, ...schemes.split, ...schemes.analogous, ...schemes.triadic, ...schemes.quadratic, ...schemes.monochrome ]; const seen = new Set(); const out = []; for (const c of all) { const key = c.hex.toUpperCase(); if (!seen.has(key)) { seen.add(key); out.push(c); } if (out.length >= limit) break; } return out; } function applyCssVars(colors, baseHex) { const root = document.documentElement; for (let i = 0; i < colors.length; i++) { root.style.setProperty( `--color-${String(i + 1).padStart(2, "0")}`, colors[i] ); } root.style.setProperty("--color-base", baseHex); } const grid = document.getElementById("backgroundGrid"); const input = document.getElementById("baseColor"); const randomize = document.getElementById("randomize"); const tabs = document.getElementById("tabs"); const toast = document.getElementById("toast"); let currentFilter = "split"; let currentSchemes = null; function showToast(msg) { toast.textContent = msg; toast.classList.add("show"); setTimeout(() => toast.classList.remove("show"), 1400); } function layoutGrid(n) { const cols = Math.ceil(Math.sqrt(n)); const rows = Math.ceil(n / cols); grid.style.gridTemplateColumns = `repeat(${n}, 1fr)`; grid.style.gridTemplateRows = `1fr`; } function render() { const baseHex = (function () { try { return String(input.value).trim() || "#5eb0e5"; } catch (e) { return "#5eb0e5"; } })(); currentSchemes = generateSchemes(baseHex); let items = []; if (currentFilter === "all") { const uniq = uniqueAll(currentSchemes, 12); items = [ { hex: currentSchemes.base.hex, category: "base", varName: "--color-base" }, ...uniq.map((c, i) => ({ hex: c.hex, category: c.category, varName: `--color-${String(i + 1).padStart(2, "0")}` })) ]; } else { const arr = currentSchemes[currentFilter] || []; items = [ { hex: currentSchemes.base.hex, category: "base", varName: "--color-base" }, ...arr.map((c, i) => ({ hex: c.hex, category: c.category, varName: `--color-${String(i + 1).padStart(2, "0")}` })) ]; } if (items.length > 12) items = items.slice(0, 12); applyCssVars( items.map((i) => i.hex), currentSchemes.base.hex ); layoutGrid(items.length); grid.innerHTML = ""; items.forEach((it, idx) => { const cell = document.createElement("div"); cell.className = "bg-cell"; cell.style.background = it.hex; const textColor = idealTextColor(it.hex); cell.style.color = textColor; const code = document.createElement("div"); code.className = "code"; code.textContent = it.hex; code.style.color = textColor; const varEl = document.createElement("div"); varEl.className = "var"; varEl.textContent = it.varName; varEl.style.color = textColor; const copyBtn = document.createElement("button"); copyBtn.className = "copy"; copyBtn.textContent = "Copy"; copyBtn.onclick = () => { navigator.clipboard .writeText(it.hex) .then(() => showToast(it.hex + " Copied ✔")); }; copyBtn.style.background = textColor === "#FFFFFF" ? "rgba(0,0,0,0.22)" : "rgba(255,255,255,0.4)"; copyBtn.style.color = textColor === "#FFFFFF" ? "#fff" : "#0f172a"; cell.appendChild(code); cell.appendChild(varEl); cell.appendChild(copyBtn); grid.appendChild(cell); const baseHex = currentSchemes ? currentSchemes.base.hex : "#1aa4ff"; tabs.querySelectorAll(".tab").forEach((tab) => { if (tab.classList.contains("active")) { tab.style.background = baseHex; tab.style.color = idealTextColor(baseHex); } else { tab.style.background = "rgba(0, 0, 0, 0.1)"; tab.style.color = "#191919"; } }); }); const strip = document.getElementById("backgroundStrip"); strip.innerHTML = ""; if (grid.children.length > 0) { const firstColor = grid.firstElementChild.style.background; const lastColor = grid.lastElementChild.style.background; [firstColor, lastColor].forEach((color) => { const cell = document.createElement("div"); cell.className = "bg-cell"; cell.style.background = color; strip.appendChild(cell); }); } } input.addEventListener("input", () => render()); randomize.addEventListener("click", () => { const newHex = "#" + Math.floor(Math.random() * 0xffffff) .toString(16) .padStart(6, "0"); input.value = newHex; if (window.Coloris && Coloris.setInstance) { Coloris.setInstance("#baseColor", { value: newHex }); } const clrField = input.closest(".clr-field"); if (clrField) { clrField.style.color = newHex; } render(); }); tabs.addEventListener("click", (e) => { const b = e.target.closest("button.tab"); if (!b) return; tabs.querySelectorAll(".tab").forEach((t) => t.classList.remove("active")); b.classList.add("active"); currentFilter = b.dataset.filter; render(); }); document.getElementById("copyCss").addEventListener("click", () => { if (!currentSchemes) return; let items = []; if (currentFilter === "all") { const uniq = uniqueAll(currentSchemes, 12); items = [currentSchemes.base.hex, ...uniq.map((c) => c.hex)]; } else { const arr = currentSchemes[currentFilter] || []; items = [currentSchemes.base.hex, ...arr.map((c) => c.hex)]; } if (items.length > 12) items = items.slice(0, 12); const lines = [":root {", ` --color-base: ${items[0]};`]; items .slice(1) .forEach((h, i) => lines.push(` --color-${String(i + 1).padStart(2, "0")}: ${h};`) ); lines.push("}"); navigator.clipboard .writeText(lines.join("\n")) .then(() => showToast("CSS Variables Copied ✔")); }); document.getElementById("copyHexes").addEventListener("click", () => { if (!currentSchemes) return; let items = []; if (currentFilter === "all") { const uniq = uniqueAll(currentSchemes, 12); items = [currentSchemes.base.hex, ...uniq.map((c) => c.hex)]; } else { const arr = currentSchemes[currentFilter] || []; items = [currentSchemes.base.hex, ...arr.map((c) => c.hex)]; } if (items.length > 12) items = items.slice(0, 12); navigator.clipboard .writeText(items.join(", ")) .then(() => showToast("HEX Values Copied ✔")); }); try { if (window.Coloris) { Coloris({ el: "#baseColor", themeMode: "dark", format: "hex", formatToggle: true, swatches: ["#aee1cd", "#ffe681", "#5eb0e5", "#ee7762", "#ba0c2e"] }); } } catch (e) {} render(); let demoInterval = null; let demoActive = true; function stopDemo() { demoActive = false; if (demoInterval) { clearInterval(demoInterval); demoInterval = null; } ["click", "keydown", "touchstart", "pointerdown"].forEach((evt) => window.removeEventListener(evt, stopDemoHandler, { capture: true }) ); } function stopDemoHandler(e) { if (e && e.isTrusted === false) return; stopDemo(); } function startDemo(intervalMs = 2000, startDelay = 600) { if (demoInterval) return; const tabButtons = Array.from(tabs.querySelectorAll(".tab")); if (tabButtons.length === 1) return; let i = 1; setTimeout(() => { demoInterval = setInterval(() => { if (!demoActive) return; const b = tabButtons[i % tabButtons.length]; tabs .querySelectorAll(".tab") .forEach((t) => t.classList.remove("active")); b.classList.add("active"); currentFilter = b.dataset.filter; render(); i++; }, intervalMs); }, startDelay); } ["click", "keydown", "touchstart", "pointerdown"].forEach((evt) => window.addEventListener(evt, stopDemoHandler, { capture: true }) ); if ( document.readyState === "complete" || document.readyState === "interactive" ) { setTimeout(() => startDemo(), 80); } else { document.addEventListener("DOMContentLoaded", () => setTimeout(() => startDemo(), 80) ); }
Step 5: Include Footer Assets
Finally, add the Coloris JS library before closing the body tag.
<script src='https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.js'></script>