Ultra Detailed Color Picker (Single File)
Ultra Detailed Color Picker
Drag, type, convert, copy — with palettes, contrast & gradients
🎯 Eyedrop
🎲 Random
⭐ Save
⬇️ Export CSS
Tip: you can type into HEX/RGB/HSL boxes — everything stays in sync.
Preview & Contrast
Aa
Lorem ipsum dolor sit amet — 16px body text.
Foreground (current color)
Palettes (Tints/Shades) & Harmonies
Gradient Builder & History
History (local)
Clear History
Pro tips: Drag on the square to set Saturation (x) and Value/Brightness (y). Use Eyedrop to sample any on‑screen color (supported in most Chromium browsers). Background picker blends your current color over it for an accurate WCAG contrast (even when transparent). Click any swatch to copy its HEX.
${hex} `;
node.appendChild(el);
});
}
function renderPalettes({r,g,b}){
// Tints/Shades via HSL lightness sweep
const baseHsl = rgbToHsl(r,g,b);
const steps = [5,10,15,25,35,50,65,75,85,92];
const ts = steps.map(L=>{
const {r:rr,g:gg,b:bb} = hslToRgb(baseHsl.h, baseHsl.s, L/100);
return toHex({r:rr,g:gg,b:bb,a:1}, false);
});
renderSwatches(tintsShades, ts);
// Analogous: h ±30, ±60
const anHues = [-60,-30,0,30,60].map(d=> ((baseHsl.h + d + 360) % 360));
const analogousArr = anHues.map(h=>{
const {r:rr,g:gg,b:bb} = hslToRgb(h, baseHsl.s, baseHsl.l);
return toHex({r:rr,g:gg,b:bb,a:1}, false);
});
renderSwatches(analogous, analogousArr);
// Complementary & Split Complementary (±150, ±210)
const comp = (baseHsl.h + 180) % 360;
const split1 = (baseHsl.h + 150) % 360;
const split2 = (baseHsl.h + 210) % 360;
const comps = [baseHsl.h, split1, comp, split2].map(h=>{
const {r:rr,g:gg,b:bb} = hslToRgb(h, baseHsl.s, baseHsl.l);
return toHex({r:rr,g:gg,b:bb,a:1}, false);
});
renderSwatches(complements, comps);
// Triad (±120) & Tetrad (±90, ±180, ±270)
const tri = [(baseHsl.h+0)%360, (baseHsl.h+120)%360, (baseHsl.h+240)%360];
const tet = [(baseHsl.h+0)%360, (baseHsl.h+90)%360, (baseHsl.h+180)%360, (baseHsl.h+270)%360];
const triT = [...tri, ...tet.filter(h=>!tri.includes(h))].map(h=>{
const {r:rr,g:gg,b:bb} = hslToRgb(h, baseHsl.s, baseHsl.l);
return toHex({r:rr,g:gg,b:bb,a:1}, false);
});
renderSwatches(triTetra, triT);
}
// ========================= Gradient Builder =========================
function updateGradient(){
const start = anyToRgba(gradStart.value) || anyToRgba(hexInput.value);
const end = anyToRgba(gradEnd.value) || {r:0,g:0,b:0,a:1};
const a = clamp(Number(gradAngle.value)||0, 0, 360);
const css = `linear-gradient(${a}deg, ${fmtRGB(start.r,start.g,start.b,start.a)} 0%, ${fmtRGB(end.r,end.g,end.b,end.a)} 100%)`;
gradPreview.firstElementChild.style.background = css;
gradCss.value = `background: ${css};`;
}
gradStart.addEventListener('change', updateGradient);
gradEnd.addEventListener('change', updateGradient);
gradAngle.addEventListener('input', updateGradient);
copyGradient.addEventListener('click', ()=> copyText(gradCss.value));
// ========================= History (LocalStorage) =========================
const HISTORY_KEY = 'ultraColorPickerHistory';
function getHistory(){ try{ return JSON.parse(localStorage.getItem(HISTORY_KEY)||'[]'); }catch{ return []; } }
function setHistory(arr){ localStorage.setItem(HISTORY_KEY, JSON.stringify(arr.slice(0, 24))); }
function renderHistory(){
const arr = getHistory();
historyWrap.innerHTML = '';
arr.forEach(hex=>{
const el = document.createElement('div'); el.className = 'swatch';
el.innerHTML = `
${hex} `;
el.addEventListener('click', ()=>{ hexInput.value = hex; setFromHex(); });
historyWrap.appendChild(el);
});
}
saveColor.addEventListener('click', ()=>{
const arr = getHistory();
if (!arr.includes(hexInput.value)) arr.unshift(hexInput.value);
setHistory(arr);
renderHistory();
toast('Saved to history');
});
clearHistoryBtn.addEventListener('click', ()=>{ setHistory([]); renderHistory(); });
// ========================= Export CSS Variables =========================
exportCss.addEventListener('click', ()=>{
const vars = [
`--color-current: ${rgbInput.value};`,
`--color-current-hex: ${hexInput.value};`
].join('\n');
const css = `:root{\n${vars}\n}`;
downloadText('color-vars.css', css);
});
function downloadText(filename, content){
const blob = new Blob([content], {type:'text/css'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url; a.download = filename; a.click();
setTimeout(()=> URL.revokeObjectURL(url), 1000);
}
// ========================= Randomize & Helpers =========================
randomize.addEventListener('click', ()=>{
state.h = Math.floor(Math.random()*360);
state.s = Math.random();
state.v = lerp(0.5, 1, Math.random());
state.a = lerp(0.6, 1, Math.random());
updateAll('rand');
});
applyRecommend.addEventListener('click', ()=>{
const c = anyToRgba(recommendChip.textContent);
if (c) setFromRgbObject({...c, a:1});
});
// Live update when background changes
bgInput.addEventListener('change', ()=> updateAll('bg'));
// ========================= Micro Toast =========================
function toast(msg){
const t = document.createElement('div');
t.textContent = msg;
Object.assign(t.style, {
position:'fixed', left:'50%', bottom:'28px', transform:'translateX(-50%)',
background:'#0c0f16', color:'#cdd6e6', padding:'10px 14px', borderRadius:'999px',
border:'1px solid #1e2432', boxShadow:'var(--shadow)', fontSize:'12px', zIndex:9999, opacity:0, transition:'opacity .2s, transform .2s'
});
document.body.appendChild(t);
requestAnimationFrame(()=>{ t.style.opacity = 1; t.style.transform = 'translateX(-50%) translateY(-4px)'; });
setTimeout(()=>{ t.style.opacity = 0; t.style.transform = 'translateX(-50%)'; setTimeout(()=> t.remove(), 180); }, 1600);
}
// ========================= Init =========================
function init(){
// Seed gradient inputs
gradStart.value = '#40E0D0';
gradEnd.value = '#7C3AED';
updateGradient();
renderHistory();
// Start from teal
state.h = 174; state.s = .72; state.v = .88; state.a = 1; bgInput.value = '#ffffff';
updateAll('init');
}
window.addEventListener('load', init);