A step-by-step guide to building a fully functional gradient generator — pick colors, set angles, and export production-ready CSS.
CSS gradients are one of the most underused tools in a developer's toolkit. A well-crafted gradient can replace images, add depth to flat designs, and create visual hierarchy — all with a few lines of CSS.
But writing gradient syntax from memory is error-prone. Do you use linear-gradient(45deg, ...) or linear-gradient(to right, ...)? What's the difference between hsl and hsla color stops?
Building a gradient generator teaches you:
This tutorial assumes basic familiarity with HTML, CSS, and JavaScript. If you understand what input[type="color"] does and can read a CSS background property, you're ready.
A gradient generator needs three things: a preview area, color pickers, and a controls section. Here's the structure:
HTML"><div class="generator">
<!-- Preview area -->
<div id="preview" class="preview"></div>
<!-- Color controls -->
<div class="controls">
<div class="color-group">
<label>Color 1</label>
<input type="color" id="color1" value="#ec4899">
</div>
<div class="color-group">
<label>Color 2</label>
<input type="color" id="color2" value="#8b5cf6">
</div>
<div class="control-group">
<label>Angle: 135°</label>
<input type="range" id="angle" min="0" max="360" value="135">
</div>
</div>
<!-- Output -->
<div class="output">
<code id="css-output">linear-gradient(135deg, #ec4899, #8b5cf6)</code>
<button id="copy-btn">Copy CSS</button>
</div>
</div>
Key structural decisions:
input type="color" — Native browser color picker. No JS library needed.input type="range" — Angle control from 0° to 360°.#preview — The large area that shows the live gradient.#css-output — The generated CSS string, ready to copy.The UI needs to be clean and functional. Here's a production-ready stylesheet:
CSS">.generator {
max-width: 600px;
margin: 0 auto;
padding: 2rem;
}
.preview {
width: 100%;
height: 200px;
border-radius: 12px;
margin-bottom: 1.5rem;
border: 2px solid #334155;
transition: background 0.15s ease;
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1.5rem;
}
.color-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.color-group label {
font-size: 0.85rem;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.color-group input[type="color"] {
width: 100%;
height: 48px;
border: 2px solid #334155;
border-radius: 8px;
cursor: pointer;
background: none;
padding: 2px;
}
.control-group {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.control-group label {
font-size: 0.85rem;
color: #94a3b8;
}
.control-group input[type="range"] {
width: 100%;
height: 8px;
-webkit-appearance: none;
background: #334155;
border-radius: 4px;
outline: none;
}
.output {
background: #1e293b;
border: 1px solid #334155;
border-radius: 8px;
padding: 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.output code {
flex: 1;
font-family: 'JetBrains Mono', monospace;
font-size: 0.85rem;
color: #e2e8f0;
word-break: break-all;
}
.output button {
background: #8b5cf6;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
white-space: nowrap;
}
.output button:hover {
background: #7c3aed;
}
Notice the CSS custom properties approach: use --accent, --surface, etc. for consistent theming across your application.
This is the core of the generator. We need to read the control values and produce a valid CSS gradient string:
JavaScript">// State object — the single source of truth
const state = {
color1: '#ec4899',
color2: '#8b5cf6',
angle: 135,
type: 'linear' // 'linear' or 'radial'
};
// Build the CSS gradient string from state
function generateGradient() {
const { color1, color2, angle, type } = state;
if (type === 'linear') {
return linear-gradient(${angle}deg, ${color1}, ${color2});
} else {
return radial-gradient(circle, ${color1}, ${color2});
}
}
// Apply the gradient to the preview
function updatePreview() {
const gradient = generateGradient();
document.getElementById('preview').style.background = gradient;
document.getElementById('css-output').textContent = gradient;
document.getElementById('angle-value').textContent = angle;
}
// Initial render
updatePreview();
Key insight: The state object is the single source of truth. Every UI control updates state, and updatePreview() reads from state — never directly from DOM inputs. This is the same pattern used by frameworks like React.
Now wire up the inputs to update the state and trigger a preview refresh:
JavaScript">// Event listeners — connect UI to state
document.getElementById('color1').addEventListener('input', (e) => {
state.color1 = e.target.value;
updatePreview();
});
document.getElementById('color2').addEventListener('input', (e) => {
state.color2 = e.target.value;
updatePreview();
});
document.getElementById('angle').addEventListener('input', (e) => {
state.angle = parseInt(e.target.value);
updatePreview();
});
// Keyboard shortcut: press 'r' to randomize
document.addEventListener('keydown', (e) => {
if (e.key === 'r' || e.key === 'R') {
state.color1 = '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
state.color2 = '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
state.angle = Math.floor(Math.random() * 360);
// Sync UI controls
document.getElementById('color1').value = state.color1;
document.getElementById('color2').value = state.color2;
document.getElementById('angle').value = state.angle;
updatePreview();
}
});
The input event (not change) fires on every keystroke/drag, giving instant visual feedback. The keyboard shortcut is a nice touch that power users will appreciate.
Randomization is a great feature for gradient generators. It helps users discover color combinations they wouldn't have tried on their own. The trick is converting a random number to a valid hex color: Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0').
Users need an easy way to grab the generated CSS. The Clipboard API makes this straightforward:
JavaScript">document.getElementById('copy-btn').addEventListener('click', async () => {
const css = document.getElementById('css-output').textContent;
try {
await navigator.clipboard.writeText(css);
// Visual feedback
const btn = document.getElementById('copy-btn');
const originalText = btn.textContent;
btn.textContent = 'Copied!';
btn.style.background = '#22c55e';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '';
}, 1500);
} catch (err) {
console.error('Failed to copy:', err);
alert('Failed to copy to clipboard');
}
});
The async/await pattern with try/catch handles both success and failure gracefully. The visual feedback (button text + color change) confirms the action to the user — this is critical UX design.
The Clipboard API requires a secure context (HTTPS) or localhost. If your page is served over plain HTTP, navigator.clipboard will throw an error. As a fallback, you can use document.execCommand('copy') with a temporary textarea, but this is deprecated.
Here's everything assembled into a single, working HTML file:
index.html"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Gradient Generator</title>
<style>
/* Paste the CSS from Step 2 here */
</style>
</head>
<body>
<div class="generator">
<!-- Paste the HTML from Step 1 here -->
</div>
<script>
// Paste the JavaScript from Steps 3-5 here
</script>
</body>
</html>
The full, working implementation is available as a single HTML file — no build step required. It includes:
The fully working CSS Gradient Generator is live and free to use:
Open Gradient Generator → View This TutorialAll tools on k1r4.space are free, open-source, and require no signup, tracking, or ads. They're built with vanilla HTML, CSS, and JavaScript — no frameworks, no build tools.
Building a gradient generator taught us several important concepts:
linear-gradient(angle, color1, color2) is the simplest form. Add more color stops for complex gradients.input type="color" and input type="range" provide rich UI without any libraries.navigator.clipboard.writeText() is the modern way to copy text. Always provide visual feedback.Math.random() * 16777215 | 0 gives you a random 0-FFFFFF number. Convert with .toString(16).padStart(6, '0').