Two-Tone Interior Color Pairing Tool
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
:root {
--navy: #1a2a4f;
--navy-dark: #0f1f3f;
--navy-deep: #0a1530;
--blue: #3B7BB0;
--blue-bright: #4a8fc7;
--blue-pale: #e8f1f8;
--blue-pale-2: #f2f7fb;
--ink: #1a1f2e;
--text: #3a4152;
--muted: #6a7285;
--surface: #f5f7fa;
--bg: #ffffff;
--rule: #dde3ec;
--rule-soft: #eaeef4;
--check: #3B7BB0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg);
color: var(--text);
font-family: 'Outfit', sans-serif;
font-size: 16px;
line-height: 1.55;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.container {
max-width: 1180px;
margin: 0 auto;
padding: 56px 32px;
}
.eyebrow {
display: inline-block;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--blue);
margin-bottom: 20px;
padding: 6px 14px;
background: var(--blue-pale);
border-radius: 999px;
}
.headline {
font-family: 'Outfit', sans-serif;
font-weight: 700;
font-size: clamp(34px, 4.8vw, 52px);
line-height: 1.1;
letter-spacing: -0.015em;
margin-bottom: 20px;
color: var(--navy);
max-width: 880px;
}
.headline span {
color: var(--blue);
}
.lede {
font-size: 17px;
line-height: 1.65;
max-width: 720px;
color: var(--text);
margin-bottom: 0;
}
.lede strong {
font-weight: 600;
color: var(--ink);
}
.rule {
height: 1px;
background: var(--rule);
margin: 52px 0;
}
.input-grid {
display: grid;
grid-template-columns: 1fr 1.15fr;
gap: 48px;
align-items: start;
}
@media (max-width: 880px) {
.input-grid {
grid-template-columns: 1fr;
gap: 36px;
}
.container {
padding: 36px 20px;
}
.rule {
margin: 40px 0;
}
}
.field-label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--navy);
margin-bottom: 12px;
letter-spacing: 0;
}
.color-input-row {
display: flex;
gap: 10px;
margin-bottom: 32px;
}
.color-picker-wrap {
position: relative;
width: 88px;
height: 56px;
border-radius: 10px;
overflow: hidden;
border: 2px solid var(--rule);
cursor: pointer;
flex-shrink: 0;
transition: border-color 0.18s;
}
.color-picker-wrap:hover {
border-color: var(--blue);
}
.color-picker-wrap input[type="color"] {
position: absolute;
top: -20px; left: -20px;
width: calc(100% + 40px);
height: calc(100% + 40px);
border: none;
cursor: pointer;
padding: 0;
}
.hex-input {
flex: 1;
height: 56px;
border: 2px solid var(--rule);
background: var(--bg);
padding: 0 18px;
font-family: 'JetBrains Mono', monospace;
font-size: 15px;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink);
border-radius: 10px;
outline: none;
transition: border-color 0.18s;
font-weight: 500;
}
.hex-input:focus {
border-color: var(--blue);
}
.preset-row {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.preset {
width: 38px; height: 38px;
border-radius: 50%;
border: 2px solid var(--rule);
cursor: pointer;
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s;
padding: 0;
background-clip: padding-box;
}
.preset:hover {
transform: scale(1.12);
border-color: var(--blue);
box-shadow: 0 4px 12px rgba(26, 42, 79, 0.18);
}
.room-preview {
position: relative;
aspect-ratio: 3/2;
border: 1px solid var(--rule);
border-radius: 12px;
overflow: hidden;
background: var(--surface);
box-shadow: 0 2px 8px rgba(26, 42, 79, 0.04);
}
.room-preview svg {
width: 100%;
height: 100%;
display: block;
}
.preview-caption {
font-size: 13.5px;
color: var(--muted);
margin-top: 12px;
line-height: 1.5;
}
.section-head {
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 36px;
}
.section-head h2 {
font-family: 'Outfit', sans-serif;
font-weight: 700;
font-size: clamp(26px, 3.4vw, 36px);
line-height: 1.1;
letter-spacing: -0.012em;
color: var(--navy);
}
.section-head .hint {
font-size: 14px;
color: var(--muted);
}
.pairings-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
@media (max-width: 880px) {
.pairings-grid {
grid-template-columns: 1fr;
gap: 18px;
}
}
.pairing-card {
background: var(--bg);
border: 2px solid var(--rule);
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
display: flex;
flex-direction: column;
}
.pairing-card:hover {
border-color: var(--blue);
transform: translateY(-3px);
box-shadow: 0 10px 28px rgba(26, 42, 79, 0.1);
}
.pairing-card.active {
border-color: var(--navy);
}
.pairing-card.active::before {
content: "In preview";
position: absolute;
top: 14px; right: 14px;
font-size: 11px;
letter-spacing: 0.06em;
background: var(--navy);
color: #ffffff;
padding: 5px 11px;
border-radius: 999px;
z-index: 2;
font-weight: 600;
}
.pairing-swatches {
display: flex;
height: 124px;
position: relative;
}
.pairing-swatches > div {
flex: 1;
transition: flex 0.3s ease;
}
.pairing-number {
position: absolute;
top: 14px;
left: 14px;
width: 34px;
height: 34px;
border-radius: 50%;
background: #ffffff;
color: var(--navy);
font-weight: 700;
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(26, 42, 79, 0.18);
z-index: 2;
}
.pairing-body {
padding: 22px 24px 24px;
flex: 1;
display: flex;
flex-direction: column;
}
.pairing-name {
font-family: 'Outfit', sans-serif;
font-weight: 700;
font-size: 22px;
line-height: 1.15;
margin-bottom: 8px;
color: var(--navy);
letter-spacing: -0.005em;
}
.pairing-math {
font-size: 13px;
color: var(--muted);
margin-bottom: 16px;
line-height: 1.5;
}
.pairing-hexes {
display: flex;
gap: 8px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.hex-pill {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
padding: 7px 12px;
background: var(--surface);
border: 1.5px solid var(--rule-soft);
border-radius: 6px;
cursor: pointer;
letter-spacing: 0.04em;
position: relative;
transition: all 0.15s;
user-select: none;
font-weight: 500;
color: var(--ink);
}
.hex-pill:hover {
background: var(--navy);
color: #ffffff;
border-color: var(--navy);
}
.hex-pill.copied {
background: var(--blue);
color: #ffffff;
border-color: var(--blue);
}
.hex-pill.copied::after {
content: "Copied";
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
font-size: 11px;
background: var(--navy);
color: #ffffff;
padding: 4px 10px;
border-radius: 4px;
white-space: nowrap;
font-family: 'Outfit', sans-serif;
font-weight: 500;
letter-spacing: 0.02em;
}
.pairing-use {
font-size: 14.5px;
line-height: 1.55;
color: var(--text);
padding-left: 28px;
position: relative;
}
.pairing-use::before {
content: "";
position: absolute;
left: 0;
top: 3px;
width: 18px;
height: 18px;
background: var(--blue-pale);
border-radius: 50%;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%233B7BB0' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
}
.pairing-use strong {
font-weight: 600;
color: var(--ink);
}
.callout {
margin-top: 64px;
background: var(--blue-pale);
border-left: 4px solid var(--blue);
border-radius: 10px;
padding: 28px 32px;
display: flex;
gap: 20px;
align-items: flex-start;
}
@media (max-width: 620px) {
.callout {
padding: 22px 20px;
gap: 14px;
}
}
.callout-icon {
flex-shrink: 0;
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--navy);
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 18px;
}
.callout-body {
flex: 1;
}
.callout-title {
font-family: 'Outfit', sans-serif;
font-weight: 700;
font-size: 18px;
color: var(--navy);
margin-bottom: 8px;
letter-spacing: -0.005em;
}
.callout-body p {
font-size: 15px;
line-height: 1.65;
color: var(--text);
}
Two-tone pairing tool
Find the right second color for your two-tone walls.
The four pairings that actually work indoors
Click a card to preview. Click any hex to copy.
!
Before you commit to a gallon
Monitor color is not wall color. Light reflectance value, undertones, north vs south window exposure, artificial lighting temperature, and adjacent fixed elements (flooring, trim, cabinets) all change how a paint color reads on an actual wall. Use this tool to narrow the direction, then put real samples on real walls and look at them at 7 AM, 2 PM, and 9 PM under the lights you actually live with. No digital tool — including this one — substitutes for that step.
// ---------- Color math ----------
function hexToHsl(hex) {
hex = hex.replace('#', '');
const r = parseInt(hex.substring(0, 2), 16) / 255;
const g = parseInt(hex.substring(2, 4), 16) / 255;
const b = parseInt(hex.substring(4, 6), 16) / 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = 0; s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g
{
if (t 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
// ---------- Pairing generator ----------
function getPairings(hex) {
const [h, s, l] = hexToHsl(hex);
// Monochromatic: shift lightness away from current value
const lShift = l > 50 ? -32 : 32;
const monoL = Math.min(90, Math.max(12, l + lShift));
const monochromatic = hslToHex(h, s, monoL);
// Analogous: +30 degrees on the wheel
const analogous = hslToHex(h + 30, s, l);
// Split-complementary: 150 degrees out, saturation pulled back 35%
const splitS = Math.max(s * 0.65, 18);
const splitComp = hslToHex(h + 150, splitS, l);
// Neutral pairing — warm or cool depending on source hue
const isWarm = (h 290);
const neutralH = isWarm ? 32 : 212;
const neutralL = l > 55 ? 22 : 86;
const neutral = hslToHex(neutralH, 8, neutralL);
return [
{
key: 'monochromatic',
name: 'Monochromatic',
hex: monochromatic,
math: `Same hue (${Math.round(h)}°), lightness shifted ${Math.abs(Math.round(lShift))}%.`,
use: 'Wainscoting splits and tone-on-tone trim. Lightest value on top for rooms under 9 ft. Darkest value on the bottom third for rooms over 10 ft.'
},
{
key: 'analogous',
name: 'Analogous',
hex: analogous,
math: `Hue shifted 30° on the color wheel. Saturation and lightness held constant.`,
use: 'Connected rooms and open-plan spaces. Best when you want visual flow between areas, not contrast. Main room to adjacent hallway, or living room into dining.'
},
{
key: 'splitComp',
name: 'Split-complementary',
hex: splitComp,
math: `Hue rotated 150°, saturation reduced to ${Math.round(splitS)}%.`,
use: 'Single accent wall only. Behind a headboard, sofa, or fireplace. Do not wrap this pairing around all four walls — contrast becomes fatiguing past 25% of visible surface area.'
},
{
key: 'neutral',
name: `${isWarm ? 'Warm' : 'Cool'} neutral`,
hex: neutral,
math: `${isWarm ? 'Warm' : 'Cool'} greige at 8% saturation. Value opposite the main color (LRV ~${Math.round(neutralL)}).`,
use: 'The lowest-risk two-tone pairing. Neutral on three walls with your color as a single accent, or your color on all walls with the neutral on trim, doors, and ceiling.'
}
];
}
// ---------- State + render ----------
const state = {
main: '#6b7f5c',
activeKey: 'monochromatic'
};
function render() {
const pairings = getPairings(state.main);
const activePairing = pairings.find(p => p.key === state.activeKey) || pairings[0];
// Input fields
document.getElementById('colorPicker').value = state.main;
const hexInput = document.getElementById('hexInput');
if (document.activeElement !== hexInput) {
hexInput.value = state.main.toUpperCase();
}
document.getElementById('pickerWrap').style.background = state.main;
// Room preview
document.getElementById('wallLeft').setAttribute('fill', state.main);
document.getElementById('wallRight').setAttribute('fill', activePairing.hex);
// Pairing cards
const grid = document.getElementById('pairingsGrid');
grid.innerHTML = pairings.map((p, i) => `
${p.name}
${p.math}
${state.main.toUpperCase()}
${p.hex.toUpperCase()}
${p.use}
`).join('');
// Card click → select
document.querySelectorAll('.pairing-card').forEach(card => {
card.addEventListener('click', (e) => {
if (e.target.classList.contains('hex-pill')) return;
state.activeKey = card.dataset.key;
render();
});
});
// Hex pill click → copy
document.querySelectorAll('.hex-pill').forEach(pill => {
pill.addEventListener('click', (e) => {
e.stopPropagation();
const hex = pill.dataset.hex;
const flash = () => {
pill.classList.add('copied');
setTimeout(() => pill.classList.remove('copied'), 1200);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(hex).then(flash).catch(() => {
const ta = document.createElement('textarea');
ta.value = hex; document.body.appendChild(ta);
ta.select(); document.execCommand('copy');
document.body.removeChild(ta);
flash();
});
} else {
const ta = document.createElement('textarea');
ta.value = hex; document.body.appendChild(ta);
ta.select(); document.execCommand('copy');
document.body.removeChild(ta);
flash();
}
});
});
}
// ---------- Input handlers ----------
document.getElementById('colorPicker').addEventListener('input', (e) => {
state.main = e.target.value;
render();
});
document.getElementById('hexInput').addEventListener('input', (e) => {
let val = e.target.value.trim();
if (val.length && val.charAt(0) !== '#') val = '#' + val;
if (/^#[0-9a-fA-F]{6}$/.test(val)) {
state.main = val.toLowerCase();
render();
}
});
document.getElementById('hexInput').addEventListener('blur', (e) => {
e.target.value = state.main.toUpperCase();
});
document.querySelectorAll('.preset').forEach(preset => {
preset.addEventListener('click', () => {
state.main = preset.dataset.color;
render();
});
});
// Initial render
render();