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.