diff --git a/CONTEXT.md b/CONTEXT.md
index fcc8182..4b82f5f 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -169,6 +169,18 @@ _Avoid_: input, widget, picker
To show the user what a postcode points to before the app stores it.
_Avoid_: verify, validate (for this step)
+**Check status**:
+What the WooCommerce plugin learned about the postcode of an order: `valid`, `invalid`, `unchecked` or `error`. The order meta `_gatepost_postcode_check` holds it.
+_Avoid_: verification status, validation result
+
+**Form value**:
+The text that a field gives its form when the form is sent: the canonical form of a postcode, the 6 digits of a legacy postcode, or the text without white space at the start and the end.
+_Avoid_: submitted value, output
+
+**Message catalogue**:
+A data file that holds the text of a UI in one language, one message for each key.
+_Avoid_: strings file, translations, locale file
+
**Tell**:
A habit that makes code look machine-written, such as very long lines.
_Avoid_: smell (smells are design problems), anti-pattern
diff --git a/README.md b/README.md
index edfa6db..41a8597 100644
--- a/README.md
+++ b/README.md
@@ -27,9 +27,9 @@ This repo holds the source of the site at
## What the site holds
- A [playground](https://gatepost-dev.github.io/docs/playground/) where you check a postcode in your browser.
-- Guides for the TypeScript core, the TypeScript client and the PHP package.
+- Guides for the TypeScript core, the TypeScript client, the PHP package, the HTML field and the React field.
- An API reference that the build writes from the TSDoc comments and the PHP doc comments.
-- The spec pages: the grammar, the client contract and the glossary.
+- The spec pages: the grammar, the client contract, the field and the glossary.
- A [readiness tracker](https://gatepost-dev.github.io/docs/readiness/) of common tools that reject the new postcode.
## Run it on your computer
diff --git a/astro.config.mjs b/astro.config.mjs
index 205daad..17cc43f 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -14,6 +14,8 @@ const base = '/docs';
const [coreTypeDoc, coreTypeDocGroup] = createStarlightTypeDocPlugin();
const [clientTypeDoc, clientTypeDocGroup] = createStarlightTypeDocPlugin();
+const [fieldTypeDoc, fieldTypeDocGroup] = createStarlightTypeDocPlugin();
+const [reactTypeDoc, reactTypeDocGroup] = createStarlightTypeDocPlugin();
// The API pages of a package, generated from the TSDoc comments of its source.
function typeDocOf(name) {
@@ -95,7 +97,12 @@ export default defineConfig({
// A code block that scrolls sideways needs a focus stop for keyboard users. Wrapped lines
// need none, and they fit a phone screen.
expressiveCode: { defaultProps: { wrap: true } },
- plugins: [coreTypeDoc(typeDocOf('core')), clientTypeDoc(typeDocOf('client'))],
+ plugins: [
+ coreTypeDoc(typeDocOf('core')),
+ clientTypeDoc(typeDocOf('client')),
+ fieldTypeDoc(typeDocOf('field')),
+ reactTypeDoc(typeDocOf('react')),
+ ],
sidebar: [
{ label: 'Start', items: ['start', 'playground'] },
{ label: 'Guides', items: [{ autogenerate: { directory: 'guides' } }] },
@@ -105,6 +112,8 @@ export default defineConfig({
items: [
coreTypeDocGroup,
clientTypeDocGroup,
+ fieldTypeDocGroup,
+ reactTypeDocGroup,
{
label: 'gatepost/postcode',
collapsed: true,
diff --git a/js b/js
index 3ba7fc4..0003151 160000
--- a/js
+++ b/js
@@ -1 +1 @@
-Subproject commit 3ba7fc43c4f1c15685987e88ce20cd190f95065d
+Subproject commit 00031516fc5b313e2ea2870fd3ae537a4c309f4a
diff --git a/package.json b/package.json
index a754834..6f0be2b 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"scripts": {
"sdk": "run-s sdk:js sdk:js:build sdk:php",
"sdk:js": "pnpm --dir js install --frozen-lockfile",
- "sdk:js:build": "pnpm --dir js --filter @gatepost/core --filter @gatepost/client run build",
+ "sdk:js:build": "pnpm --dir js --filter './packages/{core,client,field,react}' run build",
"sdk:php": "composer --working-dir=php install --no-interaction --no-progress",
"dev": "ASTRO_TELEMETRY_DISABLED=1 astro dev",
"build": "ASTRO_TELEMETRY_DISABLED=1 astro build",
diff --git a/php b/php
index 831f332..a1dd163 160000
--- a/php
+++ b/php
@@ -1 +1 @@
-Subproject commit 831f332f5f6441cd051094e3a5a09843978c9f51
+Subproject commit a1dd1633c7f908ae3a24f12e2bfcd214e7191953
diff --git a/scripts/spec-pages.ts b/scripts/spec-pages.ts
index 9fcd76f..e7b7c28 100644
--- a/scripts/spec-pages.ts
+++ b/scripts/spec-pages.ts
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2026 The Gatepost authors
// SPDX-License-Identifier: Apache-2.0
// Writes the spec pages of the site from the spec submodule, so that the site shows the grammar,
-// the client contract and the glossary that the SDKs implement, word for word.
+// the client contract, the field and the glossary that the SDKs implement, word for word.
import { execFileSync } from 'node:child_process';
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
@@ -25,6 +25,12 @@ const SOURCES = [
title: 'Client contract',
description: "How every Gatepost client calls NIPOST's gateway: results, errors and retries.",
},
+ {
+ file: 'field.md',
+ slug: 'field',
+ title: 'Field',
+ description: 'How every Gatepost postcode field behaves: settings, events, states and privacy.',
+ },
{
file: 'CONTEXT.md',
slug: 'glossary',
diff --git a/spec b/spec
index 9e20f87..db359cd 160000
--- a/spec
+++ b/spec
@@ -1 +1 @@
-Subproject commit 9e20f87addb2a2b238cea82d65b9bd071bedc3f6
+Subproject commit db359cde28a7c5c549f1a3069f20078f19f05a58
diff --git a/src/content/docs/guides/html.md b/src/content/docs/guides/html.md
new file mode 100644
index 0000000..d0e79b9
--- /dev/null
+++ b/src/content/docs/guides/html.md
@@ -0,0 +1,152 @@
+---
+title: Add a postcode field to a web form
+description: Use the gatepost-postcode-field element in any HTML form, with or without a framework.
+sidebar:
+ label: HTML field
+ order: 4
+---
+
+`@gatepost/field` adds ``, a form field for Nigeria's digital postcode. It works in a plain HTML form. It checks the form of the postcode while the user types, and it can ask NIPOST's gateway about the postcode.
+
+## Install
+
+The first alpha is not published yet. This page will say when it is.
+
+```sh
+pnpm add @gatepost/field
+```
+
+Import `@gatepost/field` once in your app, and the page can use the element. A page with no build step can load the one-file build from a CDN instead, as the examples below do. Pin a version in the CDN address of a production page.
+
+## Add the field
+
+```html
+
+
+```
+
+The element is empty until the script runs, so the layout moves when it loads. A page with no JavaScript sends no postcode.
+
+A `