Random Color Palette Generator Using JavaScript

Random Color Palette Generator Using JavaScript
Code Snippet:Color Palette Generator
Author: Alexandre Vacassin
Published: 5 days ago
Last Updated: September 16, 2025
Downloads: 23
License: MIT
Edit Code online: View on CodePen
Read More
This tutorial shows you how to build a random color palette generator using JavaScript. It leverages JavaScript to generate and display various color schemes based on a selected base color.

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>
Congratulations! You’ve successfully created a Random Color Palette Generator using JavaScript.
Loading... ...

Loading preview...

Device: Desktop
Dimensions: 1200x800
Lines: 0 Characters: 0 Ln 1, Ch 1

Leave a Comment

About W3Frontend

W3Frontend provides free, open-source web design code and scripts to help developers and designers build faster. Every snippet is reviewed before publishing for quality. Learn more.