Teaching a local model the GOV.UK Design System — without fine-tuning it
A model that has read the whole internet still doesn't know your design system precisely. Here's how I bring the Design System to the model at the moment it generates, behind a single sentence of prompt.
Ask any language model, local or frontier, for "a GOV.UK radios component" and you'll get something that looks right. Confident markup, sensible class names, the right general shape.
Look closely and some of those class names are invented. govuk-radio-button. govuk-form-input. Plausible, GOV.UK-flavoured, and not real. The Design System doesn't have them. A developer who pastes that markup in gets styling that silently doesn't apply.
This is the central problem of using a general model for a specific standard. The model has read a lot of HTML. It has not memorised the exact, current vocabulary of the GOV.UK Design System — and it has no way of knowing which of its confident guesses are wrong. Joe Lanman ran into the same thing in his Component Pad experiment: ask about something that doesn't exist and the model will cheerfully make it up.
You can fix this one of two ways. You can change the model — fine-tune it until the Design System is baked into its weights. Or you can change the prompt — bring the Design System to the model every time it generates. I went with the second, and this post is about how, because the "how" is most of what makes the output trustworthy.
Layer one: tell the model which classes are real
The first thing every request carries is a curated system prompt: a list of the real govuk-* classes — all 473 of them — plus the house rules for how a GOV.UK page is structured. The user never writes this. It's prepended to every generation automatically.
It is the single highest-value block of text in the whole system. In testing, adding it was the difference between near-unusable output and usable output — it's what stops the model inventing govuk-radio-button because the real class, govuk-radios__input, is right there in front of it. It's about 1,900 tokens, and it's the last thing I'd ever trim.
A list of valid classes isn't enough on its own, though. The model needs to know how a specific component is actually assembled.
Layer two: retrieve the right component, just in time
This is where retrieval comes in — "retrieval-augmented generation", if you want the term, but the idea is plainer than the name. The Design System is documented: each component has a description, its valid classes, a worked HTML example, and accessibility notes. I've turned that documentation into something the app can search.
When you ask for a check-answers page, the app retrieves the documentation for the components a check-answers page actually uses — the summary list, the right structure — and injects it into the prompt for that request. Ask for a start page and it retrieves different docs. The model isn't working from memory; it's working from the relevant page of the manual, opened to the right place, every single time.
Joe found the same thing was necessary in his experiment: his date-input component came out wrong until he added a real Design System example to the prompt. Concrete examples teach the model the things a schema or a class list can't.
The user types one sentence; the app assembles the rest
From the outside, the workflow is what you'd hope:
Create a GOV.UK start page for applying for a Blue Badge.
From the inside, that one sentence becomes a carefully assembled prompt: the 473-class system prompt, plus the retrieved component documentation, plus some page-structure guidance, plus your request. A full-page generation assembles something in the region of 3,500–4,500 tokens of context before the model writes a single word of output — and the user sees none of it. They get a rendered GOV.UK page; the scaffolding stays behind the curtain.
That's the right division of labour. The person brings intent. The app brings the standard.
Why not just fine-tune?
It's a fair question, and I'll write about it properly in a later post because it deserves its own argument. The short version: the GOV.UK Design System ships new versions. (I build against a pinned version — 5.14.0 as I write this.) When it updates, retrieval updates by re-running a script that rebuilds the class list and the component docs. A fine-tuned model freezes the house style into its weights and has to be retrained to keep up. For a living standard, "refresh the context" is a much cheaper way to stay current than "retrain the model".
The catch, which is the subject of the next post
Context isn't free. Every token of class list and component documentation is a token the model has to read before it can write, and a token of room taken away from the page it's trying to produce. On a frontier cloud model with a huge context window, you don't feel it. On a small model running on a 16GB laptop, the budget is real and it's tight.
Which leads to a genuinely surprising finding: past a certain point, adding more of this carefully-retrieved context made a small model's output worse, not better. Working out what to keep and what to cut is where this got interesting — and that's the next post.
Prompt to Page is an independent project by Courtney Allen. It is not affiliated with, endorsed by, or connected to the Government Digital Service, the Crown, or any UK government body. "GOV.UK" and the GOV.UK Design System are referenced descriptively.