Skip to content
← 1001 Widgets
N° 003 · ●○○

Palette Generator

Generates 5 harmonious colours. Hover to see the hex code, click to copy it.

HTMLCSSJS
What you'll learn
  • Think about colour in HSL
  • Convert HSL to hexadecimal
  • Copy to the clipboard with the Clipboard API
Why this language

Pourquoi ce widget ? Parce que la couleur est le sujet le plus mal compris en développement front-end. La plupart des devs copient des hex au hasard et espèrent que ça marche.

Ce widget t'apprend le modèle HSL : le seul modèle de couleur qui a un sens pour un humain :

H (Hue) = la teinte sur la roue chromatique 0° = rouge, 120° = vert, 240° = bleu, 360° = rouge S (Saturation) = l'intensité de la couleur 0% = gris, 100% = couleur pure L (Lightness) = la luminosité 0% = noir, 50% = normal, 100% = blanc

Le secret des palettes harmonieuses : Si tu prends une teinte de base et que tu espaces les autres teintes de 72° (360°/5) sur la roue, tu obtiens toujours une palette visuellement cohérente. C'est de la mathématique appliquée à l'art.

How it works
  1. GÉNÉRATION DE LA PALETTE On choisit une teinte de base aléatoire (0-360°) puis on espace les 4 autres de 72° (360°/5)

    function generatePalette() {
    const base = Math.floor(Math.random() * 360)
  2. Array.from crée un tableau de 5 éléments Pour chaque index i (0 à 4), on calcule la teinte Le % 360 garantit qu'on reste dans la roue (0-360)

    return Array.from({ length: 5 }, (_, i) => {
    const hue = (base + i * 72) % 360
  3. On garde saturation et lightness fixes pour que toutes les couleurs aient le même "poids" visuel

    return hslToHex(hue, 65, 55)
    })
    }
  4. CONVERSION HSL → HEX Le navigateur comprend hsl() mais les designers veulent des hex. Cette fonction fait la conversion. L'algorithme est mathématique : retiens juste que s et l sont en pourcentages divisés par 100.

    function hslToHex(h, s, l) {
    s /= 100
    l /= 100
  5. a = amplitude de la couleur entre lightness et saturation

    const a = s * Math.min(l, 1 - l)
  6. f() calcule le composant RGB pour R (n=0), G (n=8), B (n=4)

    const f = n => {
    const k = (n + h / 30) % 12
    const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
  7. toString(16) convertit en hexadécimal padStart(2, '0') garantit 2 chiffres (ex: 'f' → '0f')

    return Math.round(255 * color).toString(16).padStart(2, '0')
    }
    
    return '#' + f(0) + f(8) + f(4)
    }
  8. COPIE DANS LE PRESSE-PAPIER navigator.clipboard est l'API moderne (HTTPS requis) Le .then/.catch gère le succès et l'échec

    function copyToClipboard(hex) {
    navigator.clipboard.writeText(hex)
    .then(() => showFeedback('✓ ' + hex + ' copié !'))
    .catch(() => {
  9. Fallback pour les navigateurs anciens

    const input = document.createElement('input')
    input.value = hex
    document.body.appendChild(input)
    input.select()
    document.execCommand('copy')
    document.body.removeChild(input)
    showFeedback('✓ Copié !')
    })
    }
    
    function showFeedback(msg) {
    const fb = document.getElementById('feedback')
    fb.textContent = msg
  10. setTimeout pour faire disparaître le message après 1.5s

    setTimeout(() => fb.textContent = '', 1500)
    }
Pitfalls & optimisations

PIÈGE 1 : Couleurs aléatoires sans système ❌ const color = '#' + Math.floor(Math.random() * 16777215).toString(16) → RGB aléatoire = n'importe quoi. Souvent laid. → Pas de cohérence entre les couleurs.

Fix

HSL avec teintes espacées = toujours harmonieux. → Le cerveau humain perçoit les relations entre teintes.

PIÈGE 2 : Saturation et lightness variables ❌ Faire varier S et L en même temps que H → Tu obtiens des couleurs qui n'ont pas le même poids visuel : une très claire, une très sombre, une très vive...

Fix

Garde S=65% et L=55% fixes pour toute la palette. → Toutes les couleurs ont la même "force" visuelle.

Optimisation 1

Modes d'harmonie // Complémentaire : 2 couleurs opposées sur la roue (180°) function complementary(base) { return [hslToHex(base, 65, 55), hslToHex((base + 180) % 360, 65, 55)] }

// Triadique : 3 couleurs espacées de 120° function triadic(base) { return [0, 120, 240].map(offset => hslToHex((base + offset) % 360, 65, 55) ) }

// Analogue : couleurs voisines sur la roue (30°) function analogous(base) { return [-60, -30, 0, 30, 60].map(offset => hslToHex((base + offset + 360) % 360, 65, 55) ) }

Optimisation 2

Contraste WCAG // Pour savoir si du texte blanc ou noir est lisible // sur une couleur de fond, on calcule la luminance relative function getLuminance(hex) { const rgb = hex.match(/[a-f\d]{2}/gi) .map(x => parseInt(x, 16) / 255) .map(x => x <= 0.03928 ? x/12.92 : Math.pow((x+0.055)/1.055, 2.4)) return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] }

function getContrastRatio(hex1, hex2) { const l1 = getLuminance(hex1) const l2 = getLuminance(hex2) const lighter = Math.max(l1, l2) const darker = Math.min(l1, l2) return (lighter + 0.05) / (darker + 0.05) }

// WCAG AA = ratio >= 4.5 pour texte normal // WCAG AAA = ratio >= 7 pour texte normal function getTextColor(bgHex) { const onWhite = getContrastRatio(bgHex, '#ffffff') const onBlack = getContrastRatio(bgHex, '#000000') return onWhite > onBlack ? '#ffffff' : '#000000' }

→ React

ce que tu viens d'apprendre // Ton code vanilla : let palette = generatePalette() function refresh() { palette = generatePalette() render() }

// Devient en React : const [palette, setPalette] = useState(() => generatePalette()) const refresh = () => setPalette(generatePalette())

// generatePalette() est une pure fonction : // elle ne dépend d'aucun état externe. // C'est le pattern idéal pour React : // état minimal, logique pure séparée.

Customise

Edit the CSS variables and watch the result in real time.

Variants
Comments
0/1000

Loading comments…

Audience measurement

With your consent, this site measures its traffic with Google Analytics and the Meta pixel, via Google Tag Manager. Nothing is loaded until you choose, and you can change your mind at any time with the "Cookies" link at the bottom of the page.