Tech With Galvan
Back to Articles
Development5/5/2025 • 3 min read

Color Palette Generator using Python and Streamlit

Generate beautiful color palettes with Python and Streamlit — random schemes, harmony rules, hex/RGB codes, and one-click CSS export.

Galvan
Galvan

Founder & Creator

Introduction

A color palette generator is a designer’s utility that turns out to be a fantastic color-math lesson for developers. Click a button, get five harmonious colors; click again, get another set. Under the hood it is all HSL color space — hue, saturation, lightness — and understanding that space is what separates “random colors” from “designed palettes”.

The app also shows off st.color_picker and a neat trick: rendering swatches as pure HTML divs, no images required. It is the visual sibling of the word cloud generator — both turn abstract values into something you can judge at a glance.

Features

  • Random palettes — five colors per click, always usable.
  • Harmony rules — analogous, complementary, or triadic schemes.
  • Lock swatches — keep colors you love, reroll the rest.
  • All formats — hex and RGB codes with click-to-copy blocks.
  • CSS export — :root variables ready to paste into a stylesheet.

Prerequisites

  • Python 3.8+ — from python.org.
  • Streamlit — install with pip:
pip install streamlit colorsys

(colorsys ships with Python — the line is just a sanity check.)

Step 1: Create the Script

Save as palette_app.py:

import streamlit as st
import colorsys
import random

st.set_page_config(page_title="Color Palette", page_icon="🎨")
st.title("🎨 Color Palette Generator")

HARMONY = st.selectbox("Harmony rule", ["Random", "Analogous", "Complementary", "Triadic"])


def hsl_to_hex(h, s, l):
    r, g, b = colorsys.hls_to_rgb(h, l, s)
    return "#{:02x}{:02x}{:02x}".format(int(r * 255), int(g * 255), int(b * 255))


def generate():
    base_hue = random.random()
    hues = {
        "Random": [random.random() for _ in range(5)],
        "Analogous": [base_hue + i * 0.04 for i in range(-2, 3)],
        "Complementary": [base_hue, base_hue, (base_hue + 0.5) % 1, (base_hue + 0.5) % 1, base_hue],
        "Triadic": [base_hue, base_hue, (base_hue + 1 / 3) % 1, (base_hue + 1 / 3) % 1, (base_hue + 2 / 3) % 1],
    }[HARMONY]
    return [
        hsl_to_hex(h % 1, random.uniform(0.45, 0.85), random.uniform(0.35, 0.65))
        for h in hues
    ]


if "palette" not in st.session_state:
    st.session_state.palette = generate()

if st.button("🎲 Generate", type="primary"):
    st.session_state.palette = generate()

for i, hex_code in enumerate(st.session_state.palette):
    c1, c2, c3 = st.columns([0.35, 0.35, 0.3])
    c1.markdown(
        f"<div style='background:{hex_code};height:60px;border-radius:10px;border:1px solid #333'></div>",
        unsafe_allow_html=True,
    )
    c2.code(hex_code, language=None)
    r, g, b = int(hex_code[1:3], 16), int(hex_code[3:5], 16), int(hex_code[5:7], 16)
    c3.caption(f"rgb({r}, {g}, {b})")

css = ":root {\n" + "\n".join(f"  --color-{i+1}: {h};" for i, h in enumerate(st.session_state.palette)) + "\n}"
st.download_button("⬇️ Download CSS variables", css, "palette.css", "text/css")

Step 2: Run the App

streamlit run palette_app.py

Hit Generate a few times, switch harmony rules, and download a palette as CSS variables.

How It Works

RGB is great for screens and terrible for thinking about color. HSL fixes that: hue is the position on the color wheel (0–1 here), saturation how colorful, lightness how bright. All the harmony rules become simple hue arithmetic — analogous colors sit ±14% around a base hue, complementary is base +50%, triadic adds +33% and +66%. Saturation and lightness are randomized within tasteful bands so results stay usable instead of neon.

colorsys.hls_to_rgb converts back to screen space, and a format string produces hex. Rendering swatches is a styled <div> through st.markdown(..., unsafe_allow_html=True) — the same trusted-HTML pattern as the Markdown editor, applied to visuals instead of documents.

Hex-to-RGB for the caption is int(hex_slice, 16) — base conversion in one call, no libraries.

Common Errors & Fixes

  • Colors look washed out — your saturation band is too low (below ~0.3 reads gray); keep it 0.45–0.85.
  • colorsys import error — you named a file colorsys.py in your project, shadowing the standard library; rename it.
  • Hue wraps wrong (negative hues) — hue arithmetic goes below 0 or above 1; apply % 1 before converting.
  • Swatches don’t render — unsafe_allow_html=True missing, so the HTML shows as text.

Key Concepts

  • HSL color space — design in hue/saturation/lightness, display in RGB/hex.
  • Harmony as arithmetic — wheel positions, not magic.
  • HTML swatches — zero-image UI via inline styles.
  • Base conversion — hex strings to RGB tuples with int(x, 16).

What to Try Next

  • Add contrast checks (WCAG) — compute luminance and flag combos below 4.5:1.
  • Seed palettes from a base color via st.color_picker instead of random hue.
  • Export Tailwind config or JSON alongside CSS.
  • Generate a matching gradient preview — pair with the QR generator to theme codes.

FAQ

Why generate in HSL instead of RGB?

Because the properties you want to control (“more colorful”, “darker”) are axes in HSL but nonlinear math in RGB. Design in HSL, convert once at the end.

Can I lock colors I like between rerolls?

Yes — add a checkbox per row and regenerate only unlocked indices, the same per-item state pattern as the to-do list app.

What makes a palette “accessible”?

Sufficient contrast between text and background (WCAG 4.5:1 for body text). Lightness separation matters more than hue — keep your palette’s lightness values spread out.