CodeChroma All articles
Accessibility & Colour Systems

Blocky, Bright and Still Accessible: A Teletext Palette for Canvas in 2026

CodeChroma

Ceefax has a particular hold on the British imagination. If you grew up pressing 888 for subtitles or waiting for a page to cycle round, the look is lodged somewhere deep: chunky blocks, monospace text, and colours that seemed to shout directly out of the cathode-ray tube. The service closed in 2012, but its visual grammar — 40 columns, 25 rows, eight loud colours — still turns up in zines, club flyers and the occasional local campaign site.

At CodeChroma we are less interested in a pixel-perfect tribute than in what teletext can teach a modern maker. The constraints are the point. A fixed grid and a tiny palette force colour decisions early, which is often exactly what a project needs.

The palette is not a mood board

Legacy teletext used a fixed set of colours chosen for legibility on cheap hardware, not for subtlety. Exact RGB values varied by decoder, but the familiar eight are close to black, red, green, yellow, blue, magenta, cyan and white. Pure values are a useful starting point, but they are not a design system. Pure blue against black fails WCAG badly — around 2.4:1 — and that is the sort of detail a modern accessibility layer has to catch.

A better approach is to keep the teletext roles and retune the values in OKLCH. OKLCH lets you adjust lightness without dragging the hue around, so a cyan can stay recognisably cyan while becoming easier to pair with text.

Role Legacy hex 2026 token
Black #000000 oklch(18% 0.02 260)
White #FFFFFF oklch(96% 0.01 90)
Red #FF0000 oklch(60% 0.22 28)
Green #00FF00 oklch(78% 0.19 145)
Yellow #FFFF00 oklch(90% 0.18 95)
Blue #0000FF oklch(45% 0.24 265)
Magenta #FF00FF oklch(62% 0.25 330)
Cyan #00FFFF oklch(78% 0.14 195)

Those tokens are deliberately a little softer than the originals. They still read as teletext at a glance, but they give you room to build a contrast layer without turning the whole thing into a muddy compromise.

Build a mode-7 grid in Canvas

A teletext screen is a grid, so draw it as one. You do not need a library. A 40 by 25 grid with 24-pixel cells gives you a 960 by 600 canvas, which is close enough to the old proportions without forcing a particular screen size.

const teletext = {
  black: 'oklch(18% 0.02 260)',
  white: 'oklch(96% 0.01 90)',
  red: 'oklch(60% 0.22 28)',
  green: 'oklch(78% 0.19 145)',
  yellow: 'oklch(90% 0.18 95)',
  blue: 'oklch(45% 0.24 265)',
  magenta: 'oklch(62% 0.25 330)',
  cyan: 'oklch(78% 0.14 195)'
};

const COLS = 40;
const ROWS = 25;
const CELL = 24;
const canvas = document.querySelector('#tt');
const ctx = canvas.getContext('2d');
canvas.width = COLS * CELL;
canvas.height = ROWS * CELL;

Drawing a block is just a rectangle and, optionally, a centred glyph. The interesting part is choosing the ink colour automatically.

function block(x, y, colour, text = '') {
  ctx.fillStyle = colour;
  ctx.fillRect(x * CELL, y * CELL, CELL, CELL);
  if (!text) return;
  ctx.fillStyle = inkFor(colour);
  ctx.font = 'bold 18px monospace';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(text, x * CELL + CELL / 2, y * CELL + CELL / 2);
}

Check contrast in the browser, not in your head

Canvas 2D accepts CSS colours, including oklch(), in current browsers. That means you can resolve a token to RGB with a one-pixel probe canvas and run a proper WCAG contrast calculation. This is more reliable than keeping a mental list of which pairs look okay.

const probe = document.createElement('canvas');
probe.width = probe.height = 1;
const probeCtx = probe.getContext('2d', { willReadFrequently: true });

function toRgb(colour) {
  probeCtx.clearRect(0, 0, 1, 1);
  probeCtx.fillStyle = colour;
  probeCtx.fillRect(0, 0, 1, 1);
  const [r, g, b] = probeCtx.getImageData(0, 0, 1, 1).data;
  return [r, g, b];
}

function luminance([r, g, b]) {
  const channel = v => {
    const s = v / 255;
    return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
  };
  return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
}

function contrast(a, b) {
  const l1 = luminance(toRgb(a));
  const l2 = luminance(toRgb(b));
  return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}

function inkFor(background) {
  const black = teletext.black;
  const white = teletext.white;
  return contrast(background, black) >= contrast(background, white) ? black : white;
}

For most blocks, inkFor is enough: it picks whichever of black or white gives the better ratio. It does not guarantee WCAG by itself. If neither ink reaches 4.5:1 for normal text, do not fudge the numbers. Either set the text larger and accept 3:1 for large text, or place the text on a white or black inset panel inside the coloured block.

Use the look responsibly

Teletext is loud, and loud is fine when it is decorative. It is less fine when it is the only way to understand a page. A few rules keep the aesthetic on the right side of useful:

A small HTML wrapper helps:

<canvas id='tt' role='img' aria-label='Decorative teletext-style colour blocks reading CodeChroma'></canvas>
<p class='sr-only'>Decorative image. The same information is available in the text below.</p>

Where it fits on a UK maker site

This look works best in small, deliberate doses: a hero panel, a 404 page, a workshop badge, a newsletter sign-up card, a zine-style landing page for a community project. It is less suitable for long body copy or a public-sector service journey, where trust and clarity matter more than nostalgic charm.

One pattern that works well is a 40 by 25 grid that spells a short message in blocks, with a real heading above it and a text alternative below. The canvas is the garnish; the HTML is the meal. That way the teletext palette can be as bright and blocky as you like without asking anyone to decode a retro television set just to use your site.

All Articles

Related Articles

Ten High-Street Colour Recipes, From Chip Paper Cream to Chemist Cross Green

Ten High-Street Colour Recipes, From Chip Paper Cream to Chemist Cross Green

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

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

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

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