CodeChroma All articles
Accessibility & Colour Systems

Forecast-Driven Hues: Wiring Live UK Weather into an OKLCH Sketch

CodeChroma
Forecast-Driven Hues: Wiring Live UK Weather into an OKLCH Sketch

Photo: BBC, Public domain, via Wikimedia Commons

British weather is not a backdrop. It is a design input. A static autumn palette can say 'October' until the cows come home, but it cannot tell you that it is drizzling in Leeds while the wind is doing its best to remove awnings in Bristol. That is the gap a weather-reactive palette fills: not a gimmick, but a small, local signal. In late 2026, that signal is easier to wire up than it sounds. This walkthrough builds a p5.js or vanilla JavaScript sketch that pulls a UK forecast from the Met Office DataPoint API, maps rainfall, temperature and wind into an OKLCH colour system, and keeps the whole thing accessible when the feed falls over.

Why live weather feels more local than a seasonal preset

Seasonal themes are broad-brush. They work from averages, moods and marketing calendars. A weather-driven palette works from the actual place the site serves. It can lean cooler and heavier on a wet Tuesday in Cardiff, then warm up when the cloud breaks over Norwich. That difference is small, but it is legible. It makes a maker site feel attached to a location rather than merely themed for one.

The other benefit is constraint. You are not designing infinite colours. You are designing a small system that reacts to three numbers: rain, temperature and wind. That is much easier to keep accessible than a free-for-all generative palette.

The ingredients: key, endpoint, proxy, cache

Start with a Met Office DataPoint API key. Do not put that key in client-side code. Put it behind a serverless function or a small Node proxy, then fetch your own endpoint from the browser. DataPoint may hand back XML or JSON depending on the endpoint you use, so normalise the response early into a small object your sketch understands. Also cache it. A forecast does not need updating every second, and a 20-minute cache will save you a lot of grief.

// /api/forecast.js — Node-style serverless handler
const CACHE_TTL = 20 * 60 * 1000;
let cache = { at: 0, data: null };

export default async function handler(req, res) {
  const now = Date.now();
  if (cache.data && now - cache.at < CACHE_TTL) {
    return res.status(200).json({ ...cache.data, cached: true });
  }

  const key = process.env.MET_OFFICE_KEY;
  const url = `https://datapoint.example/v1/forecast?key=${key}&location=...`;

  try {
    const r = await fetch(url, { headers: { accept: 'application/json' } });
    if (!r.ok) throw new Error(`DataPoint ${r.status}`);
    const data = await r.json();
    cache = { at: now, data };
    res.status(200).json({ ...data, cached: false });
  } catch (err) {
    if (cache.data) return res.status(200).json({ ...cache.data, stale: true });
    res.status(200).json({ fallback: true, reason: 'weather-feed-unavailable' });
  }
}

Normalise the forecast into three signals

Do not pass raw weather values straight into colour maths. Clamp and normalise them first. You want rain probability from 0 to 1, temperature roughly between -5 and 30 degrees, and wind speed between 0 and 60. Then build a composite weight if you need one single driver.

const clamp = (n, min, max) => Math.min(max, Math.max(min, n));
const norm = (n, min, max) => clamp((n - min) / (max - min), 0, 1);

function weatherSignal(forecast) {
  const rain = norm(forecast.precipProbability ?? 0, 0, 100);
  const temp = norm(forecast.temperature ?? 12, -5, 30);
  const wind = norm(forecast.windSpeed ?? 0, 0, 60);
  return {
    rain,
    temp,
    wind,
    weight: rain * 0.55 + wind * 0.25 + (1 - temp) * 0.20
  };
}

Map signals to OKLCH tokens

OKLCH is useful here because lightness, chroma and hue are separate. You can let hue drift with temperature and wind while keeping text contrast under control. Do not let lightness float freely. Pin the ink colour to a dark value and test it against both the background and the surface colour. If you animate transitions, keep them slow and respect prefers-reduced-motion.

function weatherPalette({ rain, temp, wind }) {
  const hue = 210 + temp * 80 - rain * 25;
  const chroma = 0.03 + rain * 0.09;
  const bgL = 0.94 - rain * 0.12;

  return {
    '--bg': `oklch(${bgL} 0.02 ${hue})`,
    '--surface': `oklch(${Math.max(0.88, bgL - 0.04)} ${chroma * 0.5} ${hue})`,
    '--ink': `oklch(0.22 0.02 ${hue})`,
    '--accent': `oklch(${0.62 - wind * 0.08} ${0.12 + wind * 0.06} ${hue + 25})`
  };
}

Wire it into the page

Keep the application simple. Fetch the normalised forecast, build the palette, then set CSS custom properties on the document root. If you are using p5.js, you can use the same tokens for canvas fills and page chrome so the sketch and the surrounding site feel like one thing.

const root = document.documentElement;
const res = await fetch('/api/forecast');
const data = await res.json();
const palette = data.fallback
  ? defaultPalette
  : weatherPalette(weatherSignal(data.forecast));

for (const [name, value] of Object.entries(palette)) {
  root.style.setProperty(name, value);
}

Graceful fallbacks for British weather and British APIs

Use a fallback chain: fresh cache, then stale cache, then a fixed default. Never leave a blank screen. If the data is stale, label it as such rather than pretending it is live. If there is no data at all, use a calm default palette and say the forecast is unavailable. A little honesty is better than a broken mood.

async function getPalette() {
  const control = new AbortController();
  const timer = setTimeout(() => control.abort(), 4000);

  try {
    const res = await fetch('/api/forecast', { signal: control.signal });
    if (!res.ok) throw new Error('feed');
    const data = await res.json();
    return data.fallback ? defaultPalette : weatherPalette(weatherSignal(data.forecast));
  } catch {
    return defaultPalette;
  } finally {
    clearTimeout(timer);
  }
}

You can also store the last successful palette in localStorage so repeat visits do not flash from default to weather. Treat that as a cosmetic cache, not a source of truth.

Keep it usable, not theatrical

Use weather colours for surfaces, accents and illustration. Keep body copy on a stable ink-and-paper pair. Test focus states against every generated background. Avoid rapid hue shifts; the palette should feel like a window, not a lava lamp. If the result is not readable, simplify it. Accessibility is the point, not an afterthought.

A weather-reactive palette will not make a site more 'British' on its own. But it can make it more attentive to the place it serves. That is a better starting point than another seasonal preset.

All Articles

Related Articles

When the Sky Clocks Off Early: A Seasonal Palette for UK Websites

When the Sky Clocks Off Early: A Seasonal Palette for UK Websites

Beyond the Muted Middle: A Bolder Colour System That Still Behaves

Beyond the Muted Middle: A Bolder Colour System That Still Behaves

Contrast Without the Beige: An OKLCH Palette Workflow for UK Public-Sector Sites

Contrast Without the Beige: An OKLCH Palette Workflow for UK Public-Sector Sites