P02: Hexadecimal Color Visualizer
P02: Hexadecimal Color Visualizer
Transform abstract hexadecimal notation into instant visual feedback Master the relationship between hex codes and the colors they represent
Table of Contents
- Learning Objectives
- Project Overview
- What Youâll Build
- Real World Outcome
- The Core Question Youâre Answering
- Concepts You Must Understand First
- Questions to Guide Your Design
- Thinking Exercise
- The Interview Questions Theyâll Ask
- Implementation Milestones
- Hints in Layers
- Books That Will Help
- Self-Assessment Checklist
Learning Objectives
By completing this project, you will be able to:
- Convert hexadecimal to decimal fluently - Look at
FFand immediately know it means 255 - Understand the RGB color model - Explain why mixing red and green light produces yellow
- Parse and validate hex color strings - Handle
#FF5733,FF5733, and#F53correctly - Manipulate the DOM in real-time - Update visual elements as users type
- Implement bidirectional conversion - Go from hex to RGB and RGB to hex seamlessly
- Debug color-related issues - Quickly identify why a color looks wrong
- Apply this knowledge to CSS, design tools, and graphics programming
Project Overview
| Attribute | Value |
|---|---|
| Main Programming Language | JavaScript (with HTML/CSS) |
| Alternative Languages | Python with Tkinter, Python with PyQt, Rust with egui |
| Difficulty | Beginner |
| Time Estimate | A few hours |
| Prerequisites | Basic HTML, CSS, and JavaScript fundamentals |
| Core Concept | Hexadecimal in Visual Context (RGB Color Model) |
What Youâll Build
A web page with a text input for hex color codes and a large div that displays that color. As the user types a valid hex code, the color swatch updates in real-time.
+------------------------------------------------------------------+
| HEX COLOR VISUALIZER |
+------------------------------------------------------------------+
| |
| Enter Hex Color: [#FF5733 ] |
| |
| +----------------------------------------------------------+ |
| | | |
| | | |
| | | |
| | COLOR PREVIEW | |
| | | |
| | (Large color swatch) | |
| | | |
| | | |
| +----------------------------------------------------------+ |
| |
| RGB Breakdown: |
| +------------------+ +------------------+ +------------------+|
| | R: FF = 255 | | G: 57 = 87 | | B: 33 = 51 ||
| +------------------+ +------------------+ +------------------+|
| |
| [Optional: RGB Sliders] |
| Red: [===================|-------] 255 |
| Green: [=======|-------------------] 87 |
| Blue: [====|----------------------] 51 |
| |
+------------------------------------------------------------------+
Real World Outcome
When you complete this project, youâll have a fully functional color visualization tool. Hereâs exactly what youâll see:
The Interface
Large Color Swatch Behavior:
- Starts with a default color (white
#FFFFFFor a nice neutral) - Instantly updates as the user types valid hex codes
- Smooth transition between colors (optional CSS enhancement)
- Takes up at least 300x200 pixels so the color is clearly visible
Input Field Behavior:
- Accepts input with or without the
#prefix - Handles both 6-character (
FF5733) and 3-character (F53) shorthand - Shows visual feedback for invalid input (red border, warning message)
- Allows uppercase, lowercase, or mixed case
RGB Value Display:
- Shows the hex pair for each channel:
R: FF,G: 57,B: 33 - Shows the decimal equivalent:
= 255,= 87,= 51 - Updates synchronously with the color swatch
RGB Sliders (Optional Enhancement):
- Three range inputs for R, G, B (0-255 each)
- Moving a slider updates both the color swatch AND the hex input
- Demonstrates bidirectional conversion
Real-Time Updates
User types: "#FF"
-> Input valid so far, but incomplete
-> Color swatch shows nothing (or stays on previous color)
User types: "#FF5"
-> Still incomplete for 6-character format
-> Could be valid 3-character shorthand (#FF5 = #FFFF55)
User types: "#FF57"
-> Invalid (not 3 or 6 characters after #)
-> Show validation warning
User types: "#FF5733"
-> Valid! Color swatch shows coral/orange-red
-> RGB display shows: R:255, G:87, B:51
Error Handling Visual Feedback
Invalid Input: "#GGGGGG"
+------------------------------------------------------------------+
| Enter Hex Color: [#GGGGGG ] <-- Red border |
| [!] Invalid hex color code |
+------------------------------------------------------------------+
Valid Input: "#00FF00"
+------------------------------------------------------------------+
| Enter Hex Color: [#00FF00 ] <-- Green border (success) |
| Pure green - nice choice! |
+------------------------------------------------------------------+
The âAha!â Moment
When you finish this project, youâll experience the moment where hex colors âclickâ:
- Youâll see
#FF0000and instantly know itâs pure red - Youâll look at
#00FF00and recognize pure green without thinking - Youâll understand why
#808080is exactly 50% gray - Youâll see
#FFFFFFand know itâs white because all channels are maxed out - Youâll realize that
#000000is black because all light is absent
This intuition is invaluable for web development, design, and understanding digital imagery.
The Core Question Youâre Answering
âHow does a computer represent 16.7 million colors using just six characters, and why is
#FF5733the same asRGB(255, 87, 51)?â
Letâs break this down:
Why 16.7 Million Colors?
Each channel (R, G, B) uses 8 bits (1 byte)
8 bits can represent 2^8 = 256 different values (0-255)
Total combinations:
256 red values x 256 green values x 256 blue values
= 256^3
= 16,777,216 colors
This is written in hex as:
#000000 (black) to #FFFFFF (white)
Why Hex Instead of Decimal?
Decimal representation: rgb(255, 87, 51)
- Requires three separate numbers
- 3-12 characters depending on values
- Hard to parse at a glance
Hex representation: #FF5733
- Always exactly 6 characters (plus #)
- Each pair maps directly to one channel
- Compact and consistent
The key insight: 2 hex digits = 1 byte = values 0-255
FF in hex = 15*16 + 15 = 255 in decimal
00 in hex = 0*16 + 0 = 0 in decimal
The Conversion Formula
Hex pair to decimal:
[H1][H2] = (H1 * 16) + H2
Example: "5A"
5 * 16 = 80
A (10) = 10
Result: 80 + 10 = 90
Decimal to hex pair:
D / 16 = H1 (first hex digit)
D % 16 = H2 (second hex digit)
Example: 90
90 / 16 = 5 (integer division)
90 % 16 = 10 = A
Result: "5A"
Concepts You Must Understand First
1. The RGB Color Model (Additive Color Mixing)
Unlike paint (which uses subtractive mixing), light uses additive color mixing.
RED + GREEN = YELLOW
RED + BLUE = MAGENTA
GREEN + BLUE = CYAN
RED + GREEN + BLUE = WHITE
RED (R)
|
|
YELLOW | MAGENTA
\ | /
\ | /
\ | /
\ | /
\|/
+------------- GREEN (G)
/|\
/ | \
/ | \
/ | \
/ | \
CYAN (no mix)
|
|
BLUE (B)
Why RGB? Human eyes have three types of color receptors (cones) that are most sensitive to red, green, and blue wavelengths. By mixing these three âprimaryâ colors of light at different intensities, we can trick the eye into seeing virtually any color.
Book Reference:
- âThe Nature of Codeâ by Daniel Shiffman, Chapter on Color (explains RGB in the context of programming)
- âCodeâ by Charles Petzold, Chapter 25 (The Graphical Revolution)
2. 8-Bit Color Depth (0-255 Range)
Each color channel uses 8 bits, which gives us 256 possible intensity levels:
8 bits = 2^8 = 256 values
Binary: 00000000 to 11111111
Decimal: 0 to 255
Hex: 00 to FF
This means:
- 0 (00) = No light from this channel
- 255 (FF) = Maximum light from this channel
- 128 (80) = Approximately 50% intensity
Why 8 bits per channel?
- 8 bits = 1 byte = convenient for computer memory
- 256 levels per channel gives smooth gradients
- Human eyes canât distinguish more than ~10 million colors anyway
Book Reference:
- âComputer Systems: A Programmerâs Perspectiveâ by Bryant & OâHallaron, Section 2.1 (Information Storage)
3. Hexadecimal as Byte Representation
Hexadecimal (base 16) is the perfect notation for bytes because:
1 hex digit = 4 bits (a "nibble")
2 hex digits = 8 bits (a byte)
Hex Digit | Binary | Decimal
----------|--------|--------
0 | 0000 | 0
1 | 0001 | 1
2 | 0010 | 2
3 | 0011 | 3
4 | 0100 | 4
5 | 0101 | 5
6 | 0110 | 6
7 | 0111 | 7
8 | 1000 | 8
9 | 1001 | 9
A | 1010 | 10
B | 1011 | 11
C | 1100 | 12
D | 1101 | 13
E | 1110 | 14
F | 1111 | 15
Why is this important?
When you see #FF, youâre seeing the binary value 11111111 written compactly. Each hex digit represents exactly 4 bits, making conversion straightforward.
Book Reference:
- âCodeâ by Charles Petzold, Chapters 7-8 (covers number systems in depth)
4. The Hex Color Format (#RRGGBB)
The standard 6-character hex color format packs three bytes into one string:
# FF 57 33
| | |
| | +-- Blue channel (0x33 = 51)
| +------- Green channel (0x57 = 87)
+------------ Red channel (0xFF = 255)
Memory layout (3 bytes):
+--------+--------+--------+
| FF | 57 | 33 | = 24 bits total
+--------+--------+--------+
Red Green Blue
Shorthand Notation (#RGB):
#F53 expands to #FF5533
Each single digit is doubled:
F -> FF
5 -> 55
3 -> 33
5. String Parsing and Validation
A valid hex color string must satisfy these rules:
Rule 1: Optional # prefix
Rule 2: Exactly 3 or 6 hex characters after the prefix
Rule 3: Each character must be in [0-9, A-F, a-f]
Valid examples:
#FF5733, FF5733, #f53, f53, #abc123, ABC123
Invalid examples:
#GGGGGG (G is not a hex digit)
#12345 (5 characters - neither 3 nor 6)
#12345678 (8 characters - too long)
12G (G is not a hex digit)
Regular Expression for Validation:
// Matches #RGB, RGB, #RRGGBB, or RRGGBB
/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
6. Converting Hex to Decimal Programmatically
The conversion uses the positional value of each digit:
Hexadecimal is base 16, so each position is worth 16^n
For a two-digit hex value like "5A":
Position 1 (5): 5 * 16^1 = 5 * 16 = 80
Position 0 (A): 10 * 16^0 = 10 * 1 = 10
Total: 80 + 10 = 90
In JavaScript:
parseInt("5A", 16) === 90
parseInt("FF", 16) === 255
parseInt("00", 16) === 0
Book Reference:
- âEloquent JavaScriptâ by Marijn Haverbeke, Chapter 4 (Data Structures) covers parsing
7. DOM Manipulation and Event Handling
To build this visualizer, you need to understand:
// Getting elements
const input = document.getElementById('hexInput');
const swatch = document.getElementById('colorSwatch');
// Listening for input events
input.addEventListener('input', (event) => {
const value = event.target.value;
// Update the swatch
});
// Changing element styles
swatch.style.backgroundColor = '#FF5733';
// Adding/removing CSS classes
input.classList.add('invalid');
input.classList.remove('invalid');
Book Reference:
- âJavaScript: The Definitive Guideâ by David Flanagan, Chapter 15 (Scripting Documents)
Questions to Guide Your Design
Before writing code, think through these design questions:
Input Handling
- Should the input field start with a
#already in it, or should users add it themselves? - What happens if the user types lowercase letters? (Should
ff0000work the same asFF0000?) - Should you validate on every keystroke, or wait until the user finishes typing?
- How will you handle the 3-character shorthand (#RGB)?
Color Display
- What should the initial color be when the page loads?
- How large should the color swatch be to show the color clearly?
- Should the color transition smoothly, or change instantly?
- What color should the swatch show while the user is typing an incomplete hex code?
RGB Conversion
- How will you extract the R, G, and B values from a 6-character hex string?
- How will you expand a 3-character shorthand to 6 characters?
- Should you display the decimal values alongside the hex values?
User Experience
- How will you indicate invalid input? (Red border? Error message? Disabled preview?)
- Should you allow pasting colors from the clipboard?
- What keyboard shortcuts might be useful?
Enhancement Ideas
- If you add RGB sliders, how will changes to sliders update the hex input?
- Should you show complementary or analogous colors?
- Could you add a color picker eyedropper tool?
Thinking Exercise
Before you write any code, work through these conversions by hand on paper. This builds the mental model that makes hex colors intuitive.
Exercise 1: Hex to RGB Conversion
Given: #A3C2F1
Break it into components:
Step 1: Identify the pairs
R: A3
G: C2
B: F1
Step 2: Convert each pair to decimal
A3:
A = 10 (in decimal)
3 = 3
A3 = (10 * 16) + 3 = 160 + 3 = 163
C2:
C = 12
2 = 2
C2 = (12 * 16) + 2 = 192 + 2 = 194
F1:
F = 15
1 = 1
F1 = (15 * 16) + 1 = 240 + 1 = 241
Step 3: Write the RGB value
RGB(163, 194, 241)
Your turn: What color would this be? (Hint: B is highest, R and G are moderate)
Answer
This is a light sky blue - the high blue value combined with moderate red and green creates a soft, sky-like color.
Exercise 2: RGB to Hex Conversion
Given: RGB(255, 87, 51)
Convert each decimal to hex:
Step 1: Red = 255
255 / 16 = 15 remainder 15
First digit: 15 = F
Second digit: 15 = F
Red hex: FF
Step 2: Green = 87
87 / 16 = 5 remainder 7
First digit: 5
Second digit: 7
Green hex: 57
Step 3: Blue = 51
51 / 16 = 3 remainder 3
First digit: 3
Second digit: 3
Blue hex: 33
Step 4: Combine
#FF5733
Your turn: Verify this is a coral/orange-red by noting which channels are dominant.
Answer
Red (255) is maxed out, Green (87) is about 1/3, Blue (51) is about 1/5. This creates a warm coral/orange-red color with strong red influence.
Exercise 3: Shorthand Expansion
Given: #F80
Expand each character:
F -> FF
8 -> 88
0 -> 00
Result: #FF8800
This is a pure orange (max red, medium-high green, no blue)
Exercise 4: Special Colors Recognition
Fill in the blank:
#FF0000 = Pure _____ (max red, no green, no blue)
#00FF00 = Pure _____ (no red, max green, no blue)
#0000FF = Pure _____ (no red, no green, max blue)
#FFFF00 = _____ (max red, max green, no blue)
#00FFFF = _____ (no red, max green, max blue)
#FF00FF = _____ (max red, no green, max blue)
#000000 = _____ (all channels zero)
#FFFFFF = _____ (all channels max)
#808080 = _____ (all channels at 128/256 = 50%)
Answers
- #FF0000 = Pure RED
- #00FF00 = Pure GREEN (also called âlimeâ)
- #0000FF = Pure BLUE
- #FFFF00 = YELLOW (red + green)
- #00FFFF = CYAN (green + blue)
- #FF00FF = MAGENTA (red + blue)
- #000000 = BLACK (no light)
- #FFFFFF = WHITE (all light)
- #808080 = GRAY (50% gray, middle of the spectrum)
The Interview Questions Theyâll Ask
These are real questions from technical interviews related to colors, hex, and bit manipulation:
Basic Understanding
Q1: âWhy do we use hexadecimal for colors instead of decimal?â
Expected answer: Hexadecimal maps perfectly to bytes. Each hex digit represents 4 bits, so two hex digits (like FF) represent exactly one byte (8 bits). This makes it easy to see that each color channel (R, G, B) is one byte. Itâs also more compact: #FFFFFF vs rgb(255, 255, 255).
Q2: âHow many colors can be represented with 24-bit color?â
Expected answer: 2^24 = 16,777,216 colors. This comes from 8 bits per channel times 3 channels = 24 bits total.
Q3: âWhatâs the difference between #RGB and #RRGGBB?â
Expected answer: #RGB is shorthand where each digit is doubled. #F53 becomes #FF5533. It can only represent 4,096 colors (16^3) compared to 16.7 million for the full format.
Intermediate
Q4: âHow would you convert the hex value âB7â to decimal without using parseInt?â
Expected answer:
const hex = 'B7';
const decimal = (parseInt(hex[0], 16) * 16) + parseInt(hex[1], 16);
// Or manually: ('B' = 11) * 16 + ('7' = 7) = 176 + 7 = 183
Q5: âHow would you invert a color given its hex code?â
Expected answer: Subtract each channel from 255 (or FF).
function invertColor(hex) {
const r = 255 - parseInt(hex.slice(1, 3), 16);
const g = 255 - parseInt(hex.slice(3, 5), 16);
const b = 255 - parseInt(hex.slice(5, 7), 16);
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}
Q6: âHow would you calculate the luminance/brightness of a color?â
Expected answer: Use the formula that accounts for human perception:
// Perceived brightness formula (ITU-R BT.709)
const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B;
// Or simpler approximation: (R + R + B + G + G + G) / 6
Advanced
Q7: âWhy does mixing red and green light make yellow, but mixing red and green paint makes brown?â
Expected answer: Light uses additive color mixing (RGB) - adding more light makes colors brighter toward white. Paint uses subtractive color mixing (CMYK) - adding more pigment absorbs more light, making colors darker toward black. Yellow light is our perception of red + green wavelengths. Brown paint results from red + green pigments each absorbing different wavelengths.
Q8: âHow would you determine if text should be black or white based on a background color?â
Expected answer: Calculate the luminance of the background and choose high contrast text:
function getContrastColor(hexcolor) {
const r = parseInt(hexcolor.slice(1, 3), 16);
const g = parseInt(hexcolor.slice(3, 5), 16);
const b = parseInt(hexcolor.slice(5, 7), 16);
const luminance = (r * 299 + g * 587 + b * 114) / 1000;
return luminance > 128 ? '#000000' : '#FFFFFF';
}
Q9: âWhat is color depth, and why might 16-bit color look different from 24-bit?â
Expected answer: Color depth is the number of bits used to represent a color. 24-bit uses 8 bits per channel (16.7M colors). 16-bit typically uses 5-6-5 (5 bits red, 6 bits green, 5 bits blue = 65,536 colors). With fewer colors, gradients may show visible banding.
Implementation Milestones
Progress through these milestones to build understanding systematically:
Milestone 1: Static HTML Structure
Goal: Create the page layout without any JavaScript.
Deliverable:
- HTML file with input field and color swatch div
- Basic CSS styling
- Static placeholder color
Demonstrates understanding of:
- HTML document structure
- CSS styling for layout
- Semantic element selection
Milestone 2: Parse and Validate Hex Input
Goal: Accept input and validate it as a proper hex color.
Deliverable:
- JavaScript that reads input field value
- Validation function that returns true/false
- Console.log showing validation results
Demonstrates understanding of:
- String manipulation
- Regular expressions (optional)
- Character classification (hex digits)
Milestone 3: Convert Hex to Decimal RGB
Goal: Extract R, G, B decimal values from a valid hex string.
Deliverable:
- Function that takes â#FF5733â and returns {r: 255, g: 87, b: 51}
- Handle both 6-character and 3-character formats
- Console.log showing conversions
Demonstrates understanding of:
- Hexadecimal to decimal conversion
- String slicing
- Object construction
Milestone 4: Update Color Swatch
Goal: Change the swatch color based on input.
Deliverable:
- Color swatch updates as user types
- Only updates for valid hex codes
- Smooth or instant color transition
Demonstrates understanding of:
- DOM manipulation
- Event listeners
- CSS background-color property
Milestone 5: Display RGB Values
Goal: Show the breakdown of R, G, B values.
Deliverable:
- Display area showing âR: FF = 255â, etc.
- Updates synchronously with swatch
- Clear visual hierarchy
Demonstrates understanding of:
- Dynamic content updates
- Formatting numbers
- Template literals or string concatenation
Milestone 6: Add RGB Sliders (Optional Enhancement)
Goal: Allow color selection via sliders that sync with hex input.
Deliverable:
- Three range inputs (0-255)
- Moving sliders updates hex input and swatch
- Editing hex input updates slider positions
- Bidirectional synchronization works correctly
Demonstrates understanding of:
- Input type=ârangeâ elements
- Two-way data binding concepts
- Decimal to hexadecimal conversion
- Avoiding infinite update loops
Milestone 7: Polish and Error Handling
Goal: Professional-quality user experience.
Deliverable:
- Visual indication of invalid input
- Helpful error messages
- Edge case handling (empty input, special characters)
- Keyboard accessibility
Demonstrates understanding of:
- UX best practices
- Error state management
- Accessibility considerations
Hints in Layers
Each layer provides progressively more guidance. Try to complete each step before revealing the next hint.
Layer 1: Start with Static HTML
Click to reveal
Create the basic structure first. Donât worry about JavaScript yet.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hex Color Visualizer</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.color-swatch {
width: 100%;
height: 300px;
border: 2px solid #333;
border-radius: 8px;
margin: 20px 0;
transition: background-color 0.2s ease;
}
.hex-input {
font-size: 24px;
padding: 10px 15px;
font-family: monospace;
width: 200px;
text-transform: uppercase;
}
.rgb-display {
display: flex;
gap: 20px;
margin-top: 20px;
}
.channel {
padding: 10px 15px;
background: #f0f0f0;
border-radius: 4px;
font-family: monospace;
}
</style>
</head>
<body>
<h1>Hex Color Visualizer</h1>
<label for="hexInput">Enter Hex Color:</label>
<input
type="text"
id="hexInput"
class="hex-input"
value="#FF5733"
maxlength="7"
>
<div id="colorSwatch" class="color-swatch" style="background-color: #FF5733;"></div>
<div class="rgb-display">
<span class="channel" id="redChannel">R: FF = 255</span>
<span class="channel" id="greenChannel">G: 57 = 87</span>
<span class="channel" id="blueChannel">B: 33 = 51</span>
</div>
<script>
// JavaScript will go here
</script>
</body>
</html>
Layer 2: Parse the Hex String
Click to reveal
Write a function to parse and normalize the hex input:
function parseHexColor(input) {
// Remove whitespace and # prefix
let hex = input.trim();
if (hex.startsWith('#')) {
hex = hex.slice(1);
}
// Convert to uppercase for consistency
hex = hex.toUpperCase();
// Handle 3-character shorthand
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
// Validate: must be exactly 6 hex characters
if (hex.length !== 6) {
return null;
}
// Check each character is a valid hex digit
const validHex = /^[0-9A-F]{6}$/;
if (!validHex.test(hex)) {
return null;
}
return hex;
}
// Test it
console.log(parseHexColor('#FF5733')); // "FF5733"
console.log(parseHexColor('ff5733')); // "FF5733"
console.log(parseHexColor('#F53')); // "FF5533"
console.log(parseHexColor('invalid')); // null
Layer 3: Convert Hex to Decimal
Click to reveal
Extract RGB values from the normalized hex string:
function hexToRgb(hex) {
// hex should be a 6-character string like "FF5733"
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
return { r, g, b };
}
// Test it
const rgb = hexToRgb('FF5733');
console.log(rgb); // { r: 255, g: 87, b: 51 }
// Manual calculation verification:
// FF = 15*16 + 15 = 255 â
// 57 = 5*16 + 7 = 87 â
// 33 = 3*16 + 3 = 51 â
If you want to understand parseInt better, hereâs the manual version:
function hexCharToDecimal(char) {
const code = char.charCodeAt(0);
if (code >= 48 && code <= 57) { // '0'-'9'
return code - 48;
}
if (code >= 65 && code <= 70) { // 'A'-'F'
return code - 65 + 10;
}
if (code >= 97 && code <= 102) { // 'a'-'f'
return code - 97 + 10;
}
return -1; // Invalid character
}
function hexPairToDecimal(pair) {
const high = hexCharToDecimal(pair[0]);
const low = hexCharToDecimal(pair[1]);
return high * 16 + low;
}
// Test: hexPairToDecimal('FF') === 255
Layer 4: Update the Color Swatch
Click to reveal
Connect the input to the swatch:
const hexInput = document.getElementById('hexInput');
const colorSwatch = document.getElementById('colorSwatch');
function updateColor() {
const input = hexInput.value;
const hex = parseHexColor(input);
if (hex) {
// Valid hex - update the swatch
colorSwatch.style.backgroundColor = '#' + hex;
hexInput.classList.remove('invalid');
hexInput.classList.add('valid');
} else {
// Invalid hex - show error state
hexInput.classList.remove('valid');
hexInput.classList.add('invalid');
// Optionally keep the last valid color
}
}
// Listen for input events (fires on every keystroke)
hexInput.addEventListener('input', updateColor);
// Also update on page load
updateColor();
Add CSS for the validation states:
.hex-input.valid {
border-color: #22c55e;
outline-color: #22c55e;
}
.hex-input.invalid {
border-color: #ef4444;
outline-color: #ef4444;
}
Layer 5: Display RGB Values
Click to reveal
Update the RGB display when the color changes:
const redChannel = document.getElementById('redChannel');
const greenChannel = document.getElementById('greenChannel');
const blueChannel = document.getElementById('blueChannel');
function updateRgbDisplay(hex) {
const rgb = hexToRgb(hex);
// Format: "R: FF = 255"
redChannel.textContent = `R: ${hex.slice(0, 2)} = ${rgb.r}`;
greenChannel.textContent = `G: ${hex.slice(2, 4)} = ${rgb.g}`;
blueChannel.textContent = `B: ${hex.slice(4, 6)} = ${rgb.b}`;
}
// Modify updateColor to also update the RGB display
function updateColor() {
const input = hexInput.value;
const hex = parseHexColor(input);
if (hex) {
colorSwatch.style.backgroundColor = '#' + hex;
updateRgbDisplay(hex); // Add this line
hexInput.classList.remove('invalid');
hexInput.classList.add('valid');
} else {
hexInput.classList.remove('valid');
hexInput.classList.add('invalid');
}
}
Layer 6: Add RGB Sliders (Optional)
Click to reveal
Add bidirectional conversion with sliders:
<!-- Add to HTML -->
<div class="sliders">
<div class="slider-row">
<label for="redSlider">R:</label>
<input type="range" id="redSlider" min="0" max="255" value="255">
<span id="redValue">255</span>
</div>
<div class="slider-row">
<label for="greenSlider">G:</label>
<input type="range" id="greenSlider" min="0" max="255" value="87">
<span id="greenValue">87</span>
</div>
<div class="slider-row">
<label for="blueSlider">B:</label>
<input type="range" id="blueSlider" min="0" max="255" value="51">
<span id="blueValue">51</span>
</div>
</div>
const redSlider = document.getElementById('redSlider');
const greenSlider = document.getElementById('greenSlider');
const blueSlider = document.getElementById('blueSlider');
// Convert decimal to 2-digit hex
function toHexPair(decimal) {
return decimal.toString(16).padStart(2, '0').toUpperCase();
}
// Convert RGB to hex string
function rgbToHex(r, g, b) {
return toHexPair(r) + toHexPair(g) + toHexPair(b);
}
// Flag to prevent infinite loops
let isUpdating = false;
function updateFromSliders() {
if (isUpdating) return;
isUpdating = true;
const r = parseInt(redSlider.value);
const g = parseInt(greenSlider.value);
const b = parseInt(blueSlider.value);
const hex = rgbToHex(r, g, b);
hexInput.value = '#' + hex;
colorSwatch.style.backgroundColor = '#' + hex;
updateRgbDisplay(hex);
// Update value labels
document.getElementById('redValue').textContent = r;
document.getElementById('greenValue').textContent = g;
document.getElementById('blueValue').textContent = b;
hexInput.classList.remove('invalid');
hexInput.classList.add('valid');
isUpdating = false;
}
function updateSliders(hex) {
if (isUpdating) return;
isUpdating = true;
const rgb = hexToRgb(hex);
redSlider.value = rgb.r;
greenSlider.value = rgb.g;
blueSlider.value = rgb.b;
document.getElementById('redValue').textContent = rgb.r;
document.getElementById('greenValue').textContent = rgb.g;
document.getElementById('blueValue').textContent = rgb.b;
isUpdating = false;
}
// Add event listeners
redSlider.addEventListener('input', updateFromSliders);
greenSlider.addEventListener('input', updateFromSliders);
blueSlider.addEventListener('input', updateFromSliders);
// Modify updateColor to also update sliders
function updateColor() {
if (isUpdating) return;
const input = hexInput.value;
const hex = parseHexColor(input);
if (hex) {
colorSwatch.style.backgroundColor = '#' + hex;
updateRgbDisplay(hex);
updateSliders(hex); // Add this line
hexInput.classList.remove('invalid');
hexInput.classList.add('valid');
} else {
hexInput.classList.remove('valid');
hexInput.classList.add('invalid');
}
}
Layer 7: Add Better Validation and UX
Click to reveal
Add helpful error messages and polish:
<!-- Add error message element -->
<div id="errorMessage" class="error-message"></div>
.error-message {
color: #ef4444;
font-size: 14px;
margin-top: 8px;
min-height: 20px;
}
const errorMessage = document.getElementById('errorMessage');
function validateAndShowErrors(input) {
let hex = input.trim();
// Check for empty input
if (hex === '' || hex === '#') {
errorMessage.textContent = '';
return null;
}
// Remove # if present
if (hex.startsWith('#')) {
hex = hex.slice(1);
}
// Check length
if (hex.length !== 3 && hex.length !== 6) {
if (hex.length < 6) {
errorMessage.textContent = 'Keep typing... need 3 or 6 characters';
} else {
errorMessage.textContent = 'Too long! Hex colors are 3 or 6 characters';
}
return null;
}
// Check for invalid characters
const invalidChars = hex.match(/[^0-9A-Fa-f]/g);
if (invalidChars) {
const unique = [...new Set(invalidChars)];
errorMessage.textContent = `Invalid character(s): ${unique.join(', ')}`;
return null;
}
// Valid!
errorMessage.textContent = '';
// Expand shorthand
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
return hex.toUpperCase();
}
// Replace parseHexColor in updateColor with validateAndShowErrors
function updateColor() {
if (isUpdating) return;
const input = hexInput.value;
const hex = validateAndShowErrors(input);
if (hex) {
colorSwatch.style.backgroundColor = '#' + hex;
updateRgbDisplay(hex);
updateSliders(hex);
hexInput.classList.remove('invalid');
hexInput.classList.add('valid');
} else if (hexInput.value.trim() !== '' && hexInput.value.trim() !== '#') {
hexInput.classList.remove('valid');
hexInput.classList.add('invalid');
} else {
// Empty or just # - neutral state
hexInput.classList.remove('valid', 'invalid');
}
}
Books That Will Help
| Topic | Book | Chapter/Section | What Youâll Learn |
|---|---|---|---|
| Number Systems | âCodeâ by Charles Petzold | Chapters 7-8 | Deep understanding of binary, hex, and why they matter |
| RGB Color Theory | âThe Nature of Codeâ by Daniel Shiffman | Color chapter | How RGB works in programming contexts |
| JavaScript DOM | âEloquent JavaScriptâ by Marijn Haverbeke | Chapter 14 | DOM manipulation fundamentals |
| JavaScript Events | âJavaScript: The Definitive Guideâ by David Flanagan | Chapter 15 | Event handling patterns |
| CSS Fundamentals | âCSS: The Definitive Guideâ by Eric Meyer | Color chapter | CSS color formats and conversions |
| Bit Manipulation | âCSAPPâ by Bryant & OâHallaron | Section 2.1 | How bytes and bits work at the hardware level |
| Color Science | âColor Scienceâ by Wyszecki & Stiles | Chapter 3 | The physics of human color perception (advanced) |
Quick References
For hex color basics:
- MDN Web Docs: âUsing CSS colorâ (online reference)
- CSS-Tricks: âHSL color wheelâ (for understanding color spaces)
For JavaScript:
- JavaScript.info: âDocumentâ section (DOM tutorials)
- MDN: parseInt() documentation
Self-Assessment Checklist
Before considering this project complete, verify:
Conceptual Understanding
- I can look at #FF0000 and immediately know itâs red without calculating
- I can explain why #808080 is exactly 50% gray
- I understand why there are 16.7 million possible colors in 24-bit color
- I can convert any 2-digit hex value to decimal in my head (e.g., 7F = 127)
- I can explain why mixing red and green light makes yellow
Implementation Skills
- My visualizer correctly parses both #RRGGBB and #RGB formats
- Invalid input shows appropriate error feedback
- The color swatch updates in real-time as the user types
- RGB decimal values display correctly for any valid hex color
- Case doesnât matter (ff0000 works the same as FF0000)
Edge Cases Handled
- Empty input doesnât cause errors
- Partial input (like â#FF5â) shows appropriate state
- Invalid characters (like G, H, etc.) are caught
- Extra characters (more than 6) are handled properly
Code Quality
- Functions are small and do one thing
- Variable names clearly indicate purpose
- No global variable pollution (use const/let, not var)
- Event listeners are properly attached
Bonus Achievements (Optional)
- RGB sliders work bidirectionally with hex input
- Color transitions are smooth (CSS transition)
- Complementary color is displayed
- âCopy to clipboardâ button works
- Contrast ratio is calculated and displayed
Extension Ideas
Once youâve completed the basic visualizer, consider these enhancements:
1. Color Harmony Display
Show complementary, analogous, and triadic colors based on the current selection.
2. Color Blindness Simulator
Show how the color appears to people with different types of color blindness (protanopia, deuteranopia, tritanopia).
3. Named Color Lookup
If the hex matches a CSS named color (like âcoralâ or âtomatoâ), display that name.
4. Color History
Keep a list of recently used colors that the user can click to reuse.
5. Random Color Generator
Add a âRandomâ button that generates a random valid hex color.
6. HSL Mode
Add an alternative input mode using HSL (Hue, Saturation, Lightness) values.
7. Accessibility Checker
Calculate the contrast ratio between the chosen color and white/black text, showing WCAG compliance level.
The âAha!â Moment
When you complete this project, youâll have internalized something that many developers never truly understand: hexadecimal isnât mysterious, itâs just a convenient way to write bytes.
Every time you see a hex color code, youâll automatically decompose it:
- First pair = Red intensity
- Second pair = Green intensity
- Third pair = Blue intensity
And youâll know that:
#000000is black because all channels are zero (no light)#FFFFFFis white because all channels are maxed (full light)#FF0000,#00FF00,#0000FFare pure red, green, blue- Equal R, G, B values produce grays (#888888, #AAAAAA, etc.)
This understanding transfers directly to:
- CSS styling
- Image manipulation libraries
- Graphics programming
- Understanding how digital images work
- Debugging color-related issues
Youâve not just built a tool. Youâve rewired your brain to see hex colors intuitively.
Project Guide Version 1.0 - December 2025