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 —
:rootvariables 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.
colorsysimport error — you named a filecolorsys.pyin your project, shadowing the standard library; rename it.- Hue wraps wrong (negative hues) — hue arithmetic goes below 0 or above 1; apply
% 1before converting. - Swatches don’t render —
unsafe_allow_html=Truemissing, 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_pickerinstead 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.