DEV Community

Cover image for Seasonal Color Analysis for Developers: Soft Summer UI vs Deep Winter Dark Mode
Ali Badshah
Ali Badshah

Posted on

Seasonal Color Analysis for Developers: Soft Summer UI vs Deep Winter Dark Mode

#D8C7D8 #8FA1B3 #E6DDE3 #9B8FA3 #6D0F2B #004D40 #0B3D91 #2A1458
light magenta swatch #D8C7D8 muted blue swatch #8FA1B3 light magenta swatch #E6DDE3 mid grey swatch #9B8FA3 burgundy swatch #6D0F2B deep teal swatch #004D40 dark blue swatch #0B3D91 deep indigo swatch #2A1458

🎨 Palettes in playSoft Summer Atelier (left) vs Deep Winter Journal (right)

Seasonal color analysis sounds like something that belongs in a fashion magazine, and that is exactly why developers skip it. Underneath the vocabulary, though, it is a two-axis classification system — and it happens to answer the two questions we ask about every UI theme:

  1. Temperature — are the colors warm (yellow-based) or cool (blue-based)?
  2. Contrast + saturation — is the palette loud and high-contrast, or muted and quiet?

Once you sort palettes along those two axes, "pick a theme" becomes "pick a quadrant" — and that is a decision you can make in ten seconds.

 

Section divider bar in burgundy

🧭 1 · The map

Season Temperature Contrast Feels like Ships well as
Spring warm bright fresh, playful marketing, kids apps, onboarding
Summer cool soft calm, hazy reading apps, wellness, docs
Autumn warm muted-rich grounded, earthy food, crafts, editorial, commerce
Winter cool high sharp, precise dashboards, fintech, dev tools

Each season splits again — soft summer, cool summer, true summer, light summer; deep winter, bright winter, dark winter, cool winter — and every sub-season is its own URL:

$ tree seasonal-palettes
season
├── spring/{spring,light,warm,bright,true}-color-palette
├── summer/{summer,soft,cool,light,true}-color-palette
├── autumn/{autumn,soft,deep,dark,warm,true}-color-palette
└── winter/{winter,deep,cool,bright,dark,true}-color-palette
Enter fullscreen mode Exit fullscreen mode

 

Section divider bar in dark blue

🌫️ 2 · Soft Summer — the low-contrast reading UI

#D8C7D8 #8FA1B3 #E6DDE3 #9B8FA3
light magenta swatch #D8C7D8 muted blue swatch #8FA1B3 light magenta swatch #E6DDE3 mid grey swatch #9B8FA3

Soft summer is cool, greyed-down and deliberately low contrast — dusty mauve, slate blue, fog. It is the palette equivalent of a quiet room. Ideal for long-form reading, journaling apps, meditation timers, documentation, anything where the interface should get out of the way.

 

And it is a trap if you use it naively:

Pair Ratio Verdict
#8FA1B3 on #E6DDE3 2.00:1 🚫 unreadable
#9B8FA3 on #E6DDE3 2.31:1 🚫 unreadable
#D8C7D8 on #E6DDE3 1.21:1 🚫 invisible

Every in-palette pair fails. That does not make the palette bad — it means soft summer is a set of surfaces, and you must bring your own ink.

 

soft-summer.csspalette for atmosphere, one dark for type

:root {
  /* from the palette — surfaces only */
  --color-page:     #E6DDE3;
  --color-card:     #D8C7D8;
  --color-line:     #9B8FA3;
  --color-muted:    #8FA1B3;

  /* the color you had to add yourself */
  --color-ink:      #241F2B;   /* 12.11:1 on --color-page ✅ AAA */
  --color-ink-soft: #4A4353;   /*  7.13:1                 ✅ AAA */
}

body {
  background: var(--color-page);
  color: var(--color-ink);
  font-size: 1.0625rem;
  line-height: 1.7;
  max-width: 68ch;
}

.card {
  background: var(--color-card);
  border: 1px solid color-mix(in srgb, var(--color-line) 45%, transparent);
  border-radius: 16px;
}

.meta { color: var(--color-ink-soft); }        /* not --color-muted! */
hr    { border: 0; border-top: 1px solid var(--color-line); }
Enter fullscreen mode Exit fullscreen mode

🧪 Lab note — the muted palette colors still do real work: borders, dividers, disabled states, chart fills, hover backgrounds. They just never carry text. Write that rule into the theme file as a comment and code review gets 80% easier.

 

Section divider bar in deep indigo

❄️ 3 · Deep Winter — the dark mode that isn't grey soup

#6D0F2B #004D40 #0B3D91 #2A1458
burgundy swatch #6D0F2B deep teal swatch #004D40 dark blue swatch #0B3D91 deep indigo swatch #2A1458

Deep winter is cool, saturated and high contrast: burgundy, emerald, royal blue, deep purple, black, icy white. Those are jewel tones, and jewel tones are the reason a dark theme can look expensive instead of looking like #333.

 

The colors are dark enough to be backgrounds and saturated enough to be identities:

Color vs #FFFFFF vs #050505 Natural role
burgundy swatch #6D0F2B #6D0F2B 11.98:1 ✅ 1.70:1 destructive / danger surface
deep teal swatch #004D40 #004D40 9.83:1 ✅ 2.07:1 success surface
dark blue swatch #0B3D91 #0B3D91 10.04:1 ✅ 2.03:1 info / primary surface
deep indigo swatch #2A1458 #2A1458 15.68:1 ✅ 1.30:1 elevated background

Read that table sideways and the theme designs itself: these four are surfaces on a black canvas, each carrying white text at AAA.

 

deep-winter.cssstatus colors that are also backgrounds

:root[data-theme='deep-winter'] {
  --bg-base:      #050505;
  --bg-raised:    #14101F;
  --bg-elevated:  #2A1458;

  --ink:          #F5F7FA;   /* 18.99:1 on --bg-base ✅ AAA */
  --ink-muted:    #A9B2C3;

  --info:         #0B3D91;
  --success:      #004D40;
  --danger:       #6D0F2B;

  /* icy accents live on top of the jewel surfaces */
  --info-ink:     #DCE9FF;
  --success-ink:  #D6F5EE;
  --danger-ink:   #FFE1E8;
}

.alert {
  border-radius: 12px;
  padding: 0.9rem 1.1rem;
  border-left: 4px solid currentColor;
}
.alert--info    { background: var(--info);    color: var(--info-ink); }
.alert--success { background: var(--success); color: var(--success-ink); }
.alert--danger  { background: var(--danger);  color: var(--danger-ink); }
Enter fullscreen mode Exit fullscreen mode

Compare that to the default dark theme most projects ship:

- --bg-base:    #1e1e1e;   /* warm grey, no identity            */
- --info:       #2196f3;   /* material blue, seen it 10,000 times */
- --success:    #4caf50;
- --danger:     #f44336;
+ --bg-base:    #050505;   /* true black, lets jewels glow       */
+ --info:       #0B3D91;   /* royal blue, 10.04:1 with white     */
+ --success:    #004D40;   /* emerald, 9.83:1                    */
+ --danger:     #6D0F2B;   /* burgundy, 11.98:1                  */
Enter fullscreen mode Exit fullscreen mode

Same semantics, completely different product personality — and the accessibility numbers went up.

 

Section divider bar in light orange

🍂 4 · The other two quadrants, briefly

Warm AutumnLagoon Warm Autumn

#FFEAB6 #FFEEBA #FFD489 #F9BA5D
light orange swatch #FFEAB6 light yellow swatch #FFEEBA light orange swatch #FFD489 vivid orange swatch #F9BA5D

Honey, amber, clay. Near-monochrome warmth for food, artisan commerce and editorial. Total internal contrast: 1.45:1 — surfaces only, again. Pair with #3A2A12.

 

Bright SpringSpring Beach Wave

#FFFF79 #FFD2DC #56D5CC #FF8D08
light yellow swatch #FFFF79 pale red swatch #FFD2DC teal swatch #56D5CC vivid orange swatch #FF8D08

Maximum energy. Great for a launch page, exhausting for a workspace. Use one as the hero background and keep the rest for illustration.

 

Cool WinterCool Winter Harmony

#003566 #0077B6 #D0006F #C1121F
deep cyan swatch #003566 dark cyan swatch #0077B6 dark magenta swatch #D0006F dark red swatch #C1121F

Icy blue structure with a shout. This is the fintech dashboard palette: navy chrome, blue data, magenta for the one number that matters.

 

Section divider bar in vivid orange

🧮 5 · Pick your quadrant with a function

If you would rather derive the season than browse it, the two axes are computable:

 

warmth=cos(h60)where h=hue in degrees \text{warmth} = \cos(h - 60^\circ) \quad\text{where } h = \text{hue in degrees}
const toHsl = (hex) => {
  const [r, g, b] = hex.replace('#', '').match(/\w\w/g).map((h) => parseInt(h, 16) / 255);
  const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
  const l = (max + min) / 2;
  const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
  let h = 0;
  if (d !== 0) {
    if (max === r) h = ((g - b) / d) % 6;
    else if (max === g) h = (b - r) / d + 2;
    else h = (r - g) / d + 4;
  }
  return { h: (h * 60 + 360) % 360, s, l };
};

export function classify(palette) {
  const hsl = palette.map(toHsl);
  const warmth = hsl.reduce((a, c) => a + Math.cos(((c.h - 60) * Math.PI) / 180), 0) / hsl.length;
  const sat    = hsl.reduce((a, c) => a + c.s, 0) / hsl.length;
  const spread = Math.max(...hsl.map((c) => c.l)) - Math.min(...hsl.map((c) => c.l));

  const warm = warmth > 0;
  const loud = sat > 0.5 || spread > 0.55;

  if (warm && loud)   return 'spring · warm + bright';
  if (warm && !loud)  return 'autumn · warm + muted';
  if (!warm && loud)  return 'winter · cool + high contrast';
  return 'summer · cool + soft';
}

classify(['#D8C7D8', '#8FA1B3', '#E6DDE3', '#9B8FA3']); // summer · cool + soft
classify(['#6D0F2B', '#004D40', '#0B3D91', '#2A1458']); // winter · cool + high contrast
Enter fullscreen mode Exit fullscreen mode

🧪 A quick jest test so the classifier keeps its promises
import { classify } from './season.js';

test.each([
  [['#FFEAB6', '#FFEEBA', '#FFD489', '#F9BA5D'], /autumn|spring/],
  [['#003566', '#0077B6', '#D0006F', '#C1121F'], /winter/],
  [['#D8C7D8', '#8FA1B3', '#E6DDE3', '#9B8FA3'], /summer/],
])('classifies %s', (palette, expected) => {
  expect(classify(palette)).toMatch(expected);
});
Enter fullscreen mode Exit fullscreen mode

 

Section divider bar in light yellow

🎚️ 6 · Ship both seasons in one app

The strongest move is not choosing — it is mapping light mode to a soft season and dark mode to a deep one, keeping the same semantic names.

:root {                         /* soft summer, daylight */
  --page: #E6DDE3;  --card: #D8C7D8;  --ink: #241F2B;  --line: #9B8FA3;
}

@media (prefers-color-scheme: dark) {
  :root {                       /* deep winter, night */
    --page: #050505;  --card: #2A1458;  --ink: #F5F7FA;  --line: #3A2A66;
  }
}

.panel {
  background: var(--card);
  color: var(--ink);
  border: 1px solid var(--line);
}
Enter fullscreen mode Exit fullscreen mode

One component, two seasons, zero conditional logic.

 

Section divider bar in teal

🧾 The checklist

  • Decide temperature first (warm/cool), contrast second (loud/quiet).
  • Soft seasons = surfaces. Always add your own ink color.
  • Deep seasons = surfaces and status colors. Check them against white, not against each other.
  • Map soft → light mode, deep → dark mode, and keep semantic names identical.
  • Browse the sub-season pages; the sub-season is the real spec, "summer" alone is too broad.

Ready-made seasonal sets — soft summer, deep winter, warm autumn, bright spring — live on ColorFiind with HEX codes and CSS variables on every page.

 

Explore palettes by season

 

Which quadrant is your current project in? Post the four hexes below. 👇

Top comments (0)