diff --git a/.githooks/pre-commit b/.githooks/pre-commit
new file mode 100755
index 0000000000..87682b80f2
--- /dev/null
+++ b/.githooks/pre-commit
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+#
+# Pre-commit hook: keep committed docs generated files in sync with their sources.
+#
+# When a commit stages source that feeds a docs generator, this regenerates the
+# docs outputs and blocks the commit if the staged outputs are stale. The
+# generators run on every such commit; they are deterministic, so when the staged
+# outputs are already fresh the regenerated files are byte-identical and the
+# commit proceeds with nothing new to stage.
+#
+# Installed automatically by the root `npm install` (a `prepare` script sets
+# core.hooksPath). To set it by hand: git config core.hooksPath .githooks
+#
+# Covers:
+# docs/generated/component-apis/*.json from Svelte components + React/Angular wrappers
+# + shared types and controllers (libs/common)
+# docs/public/search-index.json from docs/src/content/** (MDX frontmatter)
+#
+# To add a new generator, extend SOURCE_REGEX and OUTPUTS below and run it in the
+# regenerate step. See docs/ARCHITECTURE.md.
+#
+# Limitation: this validates the working tree, not the staged snapshot. With
+# partial staging (git add -p, or staging some edits while leaving others
+# unstaged) the generators read working-tree source, which can differ from what
+# is being committed. Accepted for simplicity; see docs/ARCHITECTURE.md.
+
+set -euo pipefail
+
+cd "$(git rev-parse --show-toplevel)"
+
+# Sources that feed a generator, and the output paths those generators own.
+# extract-api scans four libs roots: Svelte components (web-components/src/components),
+# React wrappers (react-components/src/lib), Angular wrappers
+# (angular-components/src/lib/components), and shared types plus imperative-API
+# controllers (common/src/lib). build:search-index scans MDX in docs/src/content.
+SOURCE_REGEX='^(libs/(web-components/src/components|react-components/src/lib|angular-components/src/lib/components|common/src/lib)/|docs/src/content/)'
+OUTPUTS=(docs/generated/component-apis docs/public/search-index.json)
+
+# 1. Only act when staged changes touch a generator source.
+# Capture to a variable rather than piping to grep: under `set -o pipefail`, grep -q
+# can close the pipe early, making git diff exit with SIGPIPE and silently skipping
+# the check on very large commits.
+staged="$(git diff --cached --name-only)"
+if ! grep -Eq "$SOURCE_REGEX" <<<"$staged"; then
+ exit 0
+fi
+
+# 2. The generators need docs dependencies (tsx). The root `postinstall` runs
+# `npm install --prefix docs`, so a normal `npm install` at the repo root sets
+# these up for everyone and the hook always has what it needs. If they are
+# still missing the environment is not set up, so block rather than let a
+# possibly-stale file through.
+if [ ! -x docs/node_modules/.bin/tsx ]; then
+ echo "pre-commit: cannot verify generated docs files: docs dependencies missing." >&2
+ echo " run 'npm install' at the repo root and commit again." >&2
+ exit 1
+fi
+
+echo "pre-commit: checking generated docs files are up to date..." >&2
+
+# 3. Regenerate. If a generator fails to run we cannot verify the outputs are
+# fresh, so block the commit rather than letting a possibly-stale file through.
+genlog="$(mktemp)"
+if ! ( cd docs && npm --silent run extract-api && npm --silent run build:search-index ) >"$genlog" 2>&1; then
+ echo "pre-commit: cannot verify generated docs files: a generator failed to run." >&2
+ echo " ensure dependencies are installed (npm install at the repo root and in docs/)." >&2
+ sed 's/^/ | /' "$genlog" >&2 || true
+ rm -f "$genlog"
+ exit 1
+fi
+rm -f "$genlog"
+
+# 4. Stale if regenerated outputs differ from what is staged (modified tracked
+# files) or if a brand-new output was produced (untracked, e.g. a new
+# component's JSON that was never generated).
+stale=0
+if ! git diff --quiet -- "${OUTPUTS[@]}"; then stale=1; fi
+if [ -n "$(git ls-files --others --exclude-standard -- "${OUTPUTS[@]}")" ]; then stale=1; fi
+
+if [ "$stale" -ne 0 ]; then
+ echo "pre-commit: generated docs files are out of date." >&2
+ echo " the regenerated files are already in your working tree." >&2
+ echo " stage them with 'git add ${OUTPUTS[*]}' and commit again." >&2
+ echo "" >&2
+ git status --short -- "${OUTPUTS[@]}" >&2
+ exit 1
+fi
+
+exit 0
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 969da32107..46f8ffdaca 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -55,3 +55,68 @@ jobs:
if [ -d "./dist" ]; then
npm run test:pr
fi
+
+ docs-freshness:
+ name: Docs Freshness
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ permissions:
+ contents: read
+
+ env:
+ # The generators don't need browsers; skip the Playwright download in npm ci.
+ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
+
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: "24"
+ cache: npm
+ cache-dependency-path: |
+ package-lock.json
+ docs/package-lock.json
+
+ - name: Install npm 11
+ run: npm install -g npm@11
+
+ - run: npm ci
+
+ - name: Check committed docs generated files match source
+ shell: bash
+ run: |
+ # Regenerate the docs outputs from source. npm ci ran the root postinstall
+ # (npm install --prefix docs), so tsx and the docs deps are available here.
+ ( cd docs && npm run extract-api && npm run build:search-index )
+
+ # The committed generated files are the single source of truth for the docs
+ # build, so fail if regeneration changed a tracked file (drift) or produced a
+ # new untracked one (a component whose JSON was never committed). This mirrors
+ # the .githooks/pre-commit check so local and CI tell the same story.
+ outputs=(docs/generated/component-apis docs/public/search-index.json)
+ stale=0
+ if ! git diff --quiet -- "${outputs[@]}"; then stale=1; fi
+ if [ -n "$(git ls-files --others --exclude-standard -- "${outputs[@]}")" ]; then stale=1; fi
+
+ if [ "$stale" -ne 0 ]; then
+ echo "::error::Generated docs files are out of date."
+ echo "The committed files do not match what the generators produce from source."
+ echo ""
+ echo "To fix, from the repo root run:"
+ echo " npm install"
+ echo " cd docs && npm run extract-api && npm run build:search-index"
+ echo "then commit the updated files under:"
+ echo " docs/generated/component-apis/ docs/public/search-index.json"
+ echo ""
+ echo "Note: extracted fields (types, JSDoc descriptions) come from the component"
+ echo "source and are overwritten on regeneration, so don't hand-edit these files."
+ echo ""
+ git status --short -- "${outputs[@]}"
+ git add --intent-to-add -- "${outputs[@]}"
+ git diff --stat -- "${outputs[@]}"
+ git diff -- "${outputs[@]}"
+ exit 1
+ fi
+
+ echo "Generated docs files are up to date."
diff --git a/apps/prs/angular/src/routes/bugs/3610/bug3610.component.html b/apps/prs/angular/src/routes/bugs/3610/bug3610.component.html
new file mode 100644
index 0000000000..615cab11db
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3610/bug3610.component.html
@@ -0,0 +1,19 @@
+
+ Bug #3610: DatePicker month dropdown placeholder
+
+ The month dropdown placeholder should display em dashes and title case:
+ —Select a month—
+
+
+ Previously it showed --select a month-- (hyphens, lowercase).
+
+
+
+
+
+
diff --git a/apps/prs/angular/src/routes/bugs/3610/bug3610.component.ts b/apps/prs/angular/src/routes/bugs/3610/bug3610.component.ts
new file mode 100644
index 0000000000..53756ffa39
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3610/bug3610.component.ts
@@ -0,0 +1,16 @@
+import { Component } from "@angular/core";
+import { GoabBlock, GoabDatePicker, GoabFormItem, GoabText } from "@abgov/angular-components";
+
+@Component({
+ standalone: true,
+ selector: "abgov-bug3610",
+ templateUrl: "./bug3610.component.html",
+ imports: [GoabBlock, GoabDatePicker, GoabFormItem, GoabText],
+})
+export class Bug3610Component {
+ date: Date | undefined = undefined;
+
+ onDateChange(event: { value: Date | undefined }) {
+ this.date = event.value;
+ }
+}
diff --git a/apps/prs/angular/src/routes/bugs/3610/bug3610.route.json b/apps/prs/angular/src/routes/bugs/3610/bug3610.route.json
new file mode 100644
index 0000000000..8fd679af7d
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3610/bug3610.route.json
@@ -0,0 +1,6 @@
+{
+ "type": "bug",
+ "id": "3610",
+ "path": "bugs/3610",
+ "title": "DatePicker month placeholder"
+}
diff --git a/apps/prs/angular/src/routes/bugs/3683/bug3683.component.html b/apps/prs/angular/src/routes/bugs/3683/bug3683.component.html
new file mode 100644
index 0000000000..36eb088eea
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3683/bug3683.component.html
@@ -0,0 +1,32 @@
+
+ Bug #3683: Input date/time vertical alignment
+
+ The text value in date and time input types should be vertically centered in the
+ input box, matching how type=text renders. Compare each type below against the
+ reference text input.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/prs/angular/src/routes/bugs/3683/bug3683.component.ts b/apps/prs/angular/src/routes/bugs/3683/bug3683.component.ts
new file mode 100644
index 0000000000..88c41be119
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3683/bug3683.component.ts
@@ -0,0 +1,17 @@
+import { Component } from "@angular/core";
+import { GoabBlock, GoabFormItem, GoabInput, GoabText } from "@abgov/angular-components";
+
+@Component({
+ standalone: true,
+ selector: "abgov-bug3683",
+ templateUrl: "./bug3683.component.html",
+ imports: [GoabBlock, GoabFormItem, GoabInput, GoabText],
+})
+export class Bug3683Component {
+ dateVal = "2025-06-09";
+ timeVal = "09:30";
+ datetimeVal = "2025-06-09T09:30";
+ monthVal = "2025-06";
+ weekVal = "2025-W23";
+ textVal = "Reference text value";
+}
diff --git a/apps/prs/angular/src/routes/bugs/3683/bug3683.route.json b/apps/prs/angular/src/routes/bugs/3683/bug3683.route.json
new file mode 100644
index 0000000000..7687ff9b07
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3683/bug3683.route.json
@@ -0,0 +1,6 @@
+{
+ "type": "bug",
+ "id": "3683",
+ "path": "bugs/3683",
+ "title": "Input date/time vertical alignment"
+}
diff --git a/apps/prs/angular/src/routes/bugs/3763/bug3763.component.html b/apps/prs/angular/src/routes/bugs/3763/bug3763.component.html
new file mode 100644
index 0000000000..ee1ddb7d6e
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3763/bug3763.component.html
@@ -0,0 +1,155 @@
+
+
+
3763 - Percentage width expands open state too much
+
+ When a Dropdown (or DatePicker) is given a percentage width, the closed input sizes
+ correctly, but the open menu expands far past the parent and the viewport. The
+ percentage is passed straight to the Popover container, where it no longer resolves
+ against the Dropdown's parent.
+
+
+ Expected: the open width should match the closed width. Open each control below and
+ compare the open menu width to the dashed parent box.
+
+
+
+
+
+
Dropdown with width="100%"
+
+ The dropdown should be exactly as wide as the 320px dashed box, both closed and
+ open. Buggy behaviour: the open menu blows past the box.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Dropdown with width="50%"
+
+ Any percentage triggers the bug, not just 100%. The closed input is half the box;
+ the open menu should match it.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
DatePicker with width="100%"
+
+ Reported by Vidit: the same percentage-width problem affects the DatePicker. The
+ open calendar should not exceed the box width.
+
+
+
+
+
+
+
+
+
+
+
DatePicker with width="50%"
+
+ Case from Benji: with a narrow percentage the input is much smaller than the
+ calendar's natural width, so the open calendar cannot match the input and
+ overflows the box. Expected: the open calendar should not exceed the maximum
+ width, not shrink to the tiny input.
+
+
+
+
+
+
+
+
+
+
+
DatePicker with width="100%" in a wide container
+
+ Opposite case: the input is far wider than the calendar's natural width.
+ Expected: the open calendar stays at its natural width (left aligned under the
+ input), it should not stretch to fill the wide input.
+
+
+
+
+
+
+
+
+
+
+
DatePicker with an invalid width
+
+ The width "5 0%" (note the stray space) is not a valid CSS dimension. Open the
+ browser console: the DatePicker should log an error explaining the width is
+ invalid, instead of failing silently.
+
+
+
+
+
+
+
+
diff --git a/apps/prs/angular/src/routes/bugs/3763/bug3763.component.ts b/apps/prs/angular/src/routes/bugs/3763/bug3763.component.ts
new file mode 100644
index 0000000000..f280bf60c1
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3763/bug3763.component.ts
@@ -0,0 +1,57 @@
+import { CommonModule } from "@angular/common";
+import { Component } from "@angular/core";
+import {
+ GoabContainer,
+ GoabDatePicker,
+ GoabDropdown,
+ GoabDropdownItem,
+} from "@abgov/angular-components";
+import type {
+ GoabDatePickerOnChangeDetail,
+ GoabDropdownOnChangeDetail,
+} from "@abgov/ui-components-common";
+
+@Component({
+ standalone: true,
+ selector: "abgov-bug3763",
+ templateUrl: "./bug3763.component.html",
+ imports: [
+ CommonModule,
+ GoabContainer,
+ GoabDatePicker,
+ GoabDropdown,
+ GoabDropdownItem,
+ ],
+})
+export class Bug3763Component {
+ province = "";
+ halfProvince = "";
+ date = "";
+ halfDate = "";
+ wideDate = "";
+ invalidDate = "";
+
+ handleProvinceChange(detail: GoabDropdownOnChangeDetail): void {
+ this.province = detail.value || "";
+ }
+
+ handleHalfProvinceChange(detail: GoabDropdownOnChangeDetail): void {
+ this.halfProvince = detail.value || "";
+ }
+
+ handleDateChange(detail: GoabDatePickerOnChangeDetail): void {
+ this.date = detail.valueStr || "";
+ }
+
+ handleHalfDateChange(detail: GoabDatePickerOnChangeDetail): void {
+ this.halfDate = detail.valueStr || "";
+ }
+
+ handleWideDateChange(detail: GoabDatePickerOnChangeDetail): void {
+ this.wideDate = detail.valueStr || "";
+ }
+
+ handleInvalidDateChange(detail: GoabDatePickerOnChangeDetail): void {
+ this.invalidDate = detail.valueStr || "";
+ }
+}
diff --git a/apps/prs/angular/src/routes/bugs/3763/bug3763.route.json b/apps/prs/angular/src/routes/bugs/3763/bug3763.route.json
new file mode 100644
index 0000000000..66a42e959e
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3763/bug3763.route.json
@@ -0,0 +1,6 @@
+{
+ "type": "bug",
+ "id": "3763",
+ "path": "bugs/3763",
+ "title": "Percentage width expands open state"
+}
diff --git a/apps/prs/angular/src/routes/bugs/3824/bug3824.component.html b/apps/prs/angular/src/routes/bugs/3824/bug3824.component.html
new file mode 100644
index 0000000000..2fd6b46a99
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3824/bug3824.component.html
@@ -0,0 +1,10 @@
+Bug 3824 - Pagination: gap between Previous and Next buttons should be goa-space-l
+
+ The gap between Previous and Next buttons should be goa-space-l (1.5rem).
+
+
+
diff --git a/apps/prs/angular/src/routes/bugs/3824/bug3824.component.ts b/apps/prs/angular/src/routes/bugs/3824/bug3824.component.ts
new file mode 100644
index 0000000000..4d20c73489
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3824/bug3824.component.ts
@@ -0,0 +1,16 @@
+import { Component } from "@angular/core";
+import { GoabPagination, GoabText } from "@abgov/angular-components";
+
+@Component({
+ standalone: true,
+ selector: "abgov-bug3824",
+ templateUrl: "./bug3824.component.html",
+ imports: [GoabPagination, GoabText],
+})
+export class Bug3824Component {
+ pageNumber = 1;
+
+ onPageChange(event: { page: number }) {
+ this.pageNumber = event.page;
+ }
+}
diff --git a/apps/prs/angular/src/routes/bugs/3824/bug3824.route.json b/apps/prs/angular/src/routes/bugs/3824/bug3824.route.json
new file mode 100644
index 0000000000..fe8b994aaf
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/3824/bug3824.route.json
@@ -0,0 +1,6 @@
+{
+ "type": "bug",
+ "id": "3824",
+ "path": "bugs/3824",
+ "title": "Pagination button gap"
+}
diff --git a/apps/prs/angular/src/routes/bugs/4030/bug4030.component.html b/apps/prs/angular/src/routes/bugs/4030/bug4030.component.html
new file mode 100644
index 0000000000..6d4ffedbc6
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/4030/bug4030.component.html
@@ -0,0 +1,7 @@
+Bug 4030 - Footer: copyright text
+
+ The V2 footer should show a single span "© 2026 Government of Alberta".
+ The "This is a Government of Alberta Digital Service" span and "GoA" abbreviation should both be removed.
+
+
+
diff --git a/apps/prs/angular/src/routes/bugs/4030/bug4030.component.ts b/apps/prs/angular/src/routes/bugs/4030/bug4030.component.ts
new file mode 100644
index 0000000000..3dc7cf68cd
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/4030/bug4030.component.ts
@@ -0,0 +1,10 @@
+import { Component } from "@angular/core";
+import { GoabAppFooter, GoabText } from "@abgov/angular-components";
+
+@Component({
+ standalone: true,
+ selector: "abgov-bug4030",
+ templateUrl: "./bug4030.component.html",
+ imports: [GoabAppFooter, GoabText],
+})
+export class Bug4030Component {}
diff --git a/apps/prs/angular/src/routes/bugs/4030/bug4030.route.json b/apps/prs/angular/src/routes/bugs/4030/bug4030.route.json
new file mode 100644
index 0000000000..35610a06b6
--- /dev/null
+++ b/apps/prs/angular/src/routes/bugs/4030/bug4030.route.json
@@ -0,0 +1,6 @@
+{
+ "type": "bug",
+ "id": "4030",
+ "path": "bugs/4030",
+ "title": "Footer copyright text"
+}
diff --git a/apps/prs/react/src/app/routes/bugs/bug3610.route.ts b/apps/prs/react/src/app/routes/bugs/bug3610.route.ts
new file mode 100644
index 0000000000..c621f16472
--- /dev/null
+++ b/apps/prs/react/src/app/routes/bugs/bug3610.route.ts
@@ -0,0 +1,10 @@
+import Bug3610Route from "../../../routes/bugs/bug3610";
+import type { PrRouteDefinition } from "../../route-manifest";
+
+export default {
+ type: "bug",
+ id: "3610",
+ path: "bugs/3610",
+ title: "DatePicker month placeholder",
+ component: Bug3610Route,
+} satisfies PrRouteDefinition;
diff --git a/apps/prs/react/src/app/routes/bugs/bug3683.route.ts b/apps/prs/react/src/app/routes/bugs/bug3683.route.ts
new file mode 100644
index 0000000000..51a5d08b32
--- /dev/null
+++ b/apps/prs/react/src/app/routes/bugs/bug3683.route.ts
@@ -0,0 +1,10 @@
+import Bug3683Route from "../../../routes/bugs/bug3683";
+import type { PrRouteDefinition } from "../../route-manifest";
+
+export default {
+ type: "bug",
+ id: "3683",
+ path: "bugs/3683",
+ title: "Input date/time vertical alignment",
+ component: Bug3683Route,
+} satisfies PrRouteDefinition;
diff --git a/apps/prs/react/src/app/routes/bugs/bug3763.route.ts b/apps/prs/react/src/app/routes/bugs/bug3763.route.ts
new file mode 100644
index 0000000000..d0be1456d1
--- /dev/null
+++ b/apps/prs/react/src/app/routes/bugs/bug3763.route.ts
@@ -0,0 +1,10 @@
+import { Bug3763Route } from "../../../routes/bugs/bug3763";
+import type { PrRouteDefinition } from "../../route-manifest";
+
+export default {
+ type: "bug",
+ id: "3763",
+ path: "bugs/3763",
+ title: "Percentage width expands open state",
+ component: Bug3763Route,
+} satisfies PrRouteDefinition;
diff --git a/apps/prs/react/src/app/routes/bugs/bug3824.route.ts b/apps/prs/react/src/app/routes/bugs/bug3824.route.ts
new file mode 100644
index 0000000000..bd5936b87a
--- /dev/null
+++ b/apps/prs/react/src/app/routes/bugs/bug3824.route.ts
@@ -0,0 +1,10 @@
+import { Bug3824Route } from "../../../routes/bugs/bug3824";
+import type { PrRouteDefinition } from "../../route-manifest";
+
+export default {
+ type: "bug",
+ id: "3824",
+ path: "bugs/3824",
+ title: "Pagination button gap",
+ component: Bug3824Route,
+} satisfies PrRouteDefinition;
diff --git a/apps/prs/react/src/app/routes/bugs/bug4030.route.ts b/apps/prs/react/src/app/routes/bugs/bug4030.route.ts
new file mode 100644
index 0000000000..9c7f0cff5c
--- /dev/null
+++ b/apps/prs/react/src/app/routes/bugs/bug4030.route.ts
@@ -0,0 +1,10 @@
+import { Bug4030Route } from "../../../routes/bugs/bug4030";
+import type { PrRouteDefinition } from "../../route-manifest";
+
+export default {
+ type: "bug",
+ id: "4030",
+ path: "bugs/4030",
+ title: "Footer copyright text",
+ component: Bug4030Route,
+} satisfies PrRouteDefinition;
diff --git a/apps/prs/react/src/routes/bugs/bug3610.tsx b/apps/prs/react/src/routes/bugs/bug3610.tsx
new file mode 100644
index 0000000000..2a60651142
--- /dev/null
+++ b/apps/prs/react/src/routes/bugs/bug3610.tsx
@@ -0,0 +1,32 @@
+import { GoabBlock, GoabDatePicker, GoabFormItem, GoabText } from "@abgov/react-components";
+import { useState } from "react";
+
+export function Bug3610Route() {
+ const [date, setDate] = useState(undefined);
+
+ return (
+
+
+ Bug #3610: DatePicker month dropdown placeholder
+
+
+ The month dropdown placeholder should display em dashes and title case:
+ —Select a month—
+
+
+ Previously it showed --select a month-- (hyphens, lowercase).
+
+
+
+ setDate(value)}
+ />
+
+
+ );
+}
+
+export default Bug3610Route;
diff --git a/apps/prs/react/src/routes/bugs/bug3683.tsx b/apps/prs/react/src/routes/bugs/bug3683.tsx
new file mode 100644
index 0000000000..0a36bb9ec8
--- /dev/null
+++ b/apps/prs/react/src/routes/bugs/bug3683.tsx
@@ -0,0 +1,79 @@
+import { useState } from "react";
+import { GoabFormItem, GoabInput, GoabBlock, GoabText } from "@abgov/react-components";
+import type { GoabInputOnChangeDetail } from "@abgov/ui-components-common";
+
+export function Bug3683Route() {
+ const [dateVal, setDateVal] = useState("2025-06-09");
+ const [timeVal, setTimeVal] = useState("09:30");
+ const [datetimeVal, setDatetimeVal] = useState("2025-06-09T09:30");
+ const [monthVal, setMonthVal] = useState("2025-06");
+ const [weekVal, setWeekVal] = useState("2025-W23");
+ const [textVal, setTextVal] = useState("Reference text value");
+
+ return (
+
+ Bug #3683: Input date/time vertical alignment
+
+ The text value in date and time input types should be vertically centered in the
+ input box, matching how type=text renders. Compare each type below against the
+ reference text input.
+
+
+
+ setTextVal(d.value)}
+ />
+
+
+
+ setDateVal(d.value)}
+ />
+
+
+
+ setTimeVal(d.value)}
+ />
+
+
+
+ setDatetimeVal(d.value)}
+ />
+
+
+
+ setMonthVal(d.value)}
+ />
+
+
+
+ setWeekVal(d.value)}
+ />
+
+
+ );
+}
+
+export default Bug3683Route;
diff --git a/apps/prs/react/src/routes/bugs/bug3763.tsx b/apps/prs/react/src/routes/bugs/bug3763.tsx
new file mode 100644
index 0000000000..237610f0ee
--- /dev/null
+++ b/apps/prs/react/src/routes/bugs/bug3763.tsx
@@ -0,0 +1,236 @@
+import { useState } from "react";
+import {
+ GoabContainer,
+ GoabDatePicker,
+ GoabDropdown,
+ GoabDropdownItem,
+ GoabText,
+} from "@abgov/react-components";
+import type {
+ GoabDatePickerOnChangeDetail,
+ GoabDropdownOnChangeDetail,
+} from "@abgov/ui-components-common";
+
+const stackStyle = {
+ display: "grid",
+ gap: "1.5rem",
+};
+
+// Narrow parent so the open-state overflow is obvious against the viewport.
+const narrowParentStyle = {
+ width: "320px",
+ border: "1px dashed var(--goa-color-greyscale-400)",
+ padding: "1rem",
+};
+
+// Wide parent so the input ends up wider than the calendar's natural width.
+const wideParentStyle = {
+ width: "800px",
+ border: "1px dashed var(--goa-color-greyscale-400)",
+ padding: "1rem",
+};
+
+export function Bug3763Route() {
+ const [province, setProvince] = useState("");
+ const [halfProvince, setHalfProvince] = useState("");
+ const [date, setDate] = useState("");
+ const [halfDate, setHalfDate] = useState("");
+ const [wideDate, setWideDate] = useState("");
+ const [invalidDate, setInvalidDate] = useState("");
+
+ const handleProvinceChange = (detail: GoabDropdownOnChangeDetail) => {
+ setProvince(detail.value || "");
+ };
+
+ const handleHalfProvinceChange = (detail: GoabDropdownOnChangeDetail) => {
+ setHalfProvince(detail.value || "");
+ };
+
+ const handleDateChange = (detail: GoabDatePickerOnChangeDetail) => {
+ setDate(detail.valueStr || "");
+ };
+
+ const handleHalfDateChange = (detail: GoabDatePickerOnChangeDetail) => {
+ setHalfDate(detail.valueStr || "");
+ };
+
+ const handleWideDateChange = (detail: GoabDatePickerOnChangeDetail) => {
+ setWideDate(detail.valueStr || "");
+ };
+
+ const handleInvalidDateChange = (detail: GoabDatePickerOnChangeDetail) => {
+ setInvalidDate(detail.valueStr || "");
+ };
+
+ return (
+
+
+ 3763 - Percentage width expands open state too much
+
+ When a Dropdown (or DatePicker) is given a percentage width, the closed input
+ sizes correctly, but the open menu expands far past the parent and the viewport.
+ The percentage is passed straight to the Popover container, where it no longer
+ resolves against the Dropdown's parent.
+
+
+ Expected: the open width should match the closed width. Open each control below
+ and compare the open menu width to the dashed parent box.
+
+
+
+
+
+
+ Dropdown with width="100%"
+
+
+ The dropdown should be exactly as wide as the 320px dashed box, both closed and
+ open. Buggy behaviour: the open menu blows past the box.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dropdown with width="50%"
+
+
+ Any percentage triggers the bug, not just 100%. The closed input is half the
+ box; the open menu should match it.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DatePicker with width="100%"
+
+
+ Reported by Vidit: the same percentage-width problem affects the DatePicker.
+ The open calendar should not exceed the box width.
+
+
+
+
+
+
+
+
+
+
+
+ DatePicker with width="50%"
+
+
+ Case from Benji: with a narrow percentage the input is much smaller than the
+ calendar's natural width, so the open calendar cannot match the input and
+ overflows the box. Expected: the open calendar should not exceed the maximum
+ width, not shrink to the tiny input.
+
+
+
+
+
+
+
+
+
+
+
+ DatePicker with width="100%" in a wide container
+
+
+ Opposite case: the input is far wider than the calendar's natural width.
+ Expected: the open calendar stays at its natural width (left aligned under the
+ input), it should not stretch to fill the wide input.
+
+
+
+
+
+
+
+
+
+
+
+ DatePicker with an invalid width
+
+
+ The width "5 0%" (note the stray space) is not a valid CSS dimension. Open the
+ browser console: the DatePicker should log an error explaining the width is
+ invalid, instead of failing silently.
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Bug3763Route;
diff --git a/apps/prs/react/src/routes/bugs/bug3824.tsx b/apps/prs/react/src/routes/bugs/bug3824.tsx
new file mode 100644
index 0000000000..c43307d658
--- /dev/null
+++ b/apps/prs/react/src/routes/bugs/bug3824.tsx
@@ -0,0 +1,22 @@
+import { GoabPagination, GoabText } from "@abgov/react-components";
+import { useState } from "react";
+
+export function Bug3824Route() {
+ const [page, setPage] = useState(1);
+
+ return (
+
+
+ Bug 3824 - Pagination: gap between Previous and Next buttons should be goa-space-l
+
+
+ The gap between Previous and Next buttons should be goa-space-l (1.5rem).
+
+ setPage(page)}
+ />
+
+ );
+}
diff --git a/apps/prs/react/src/routes/bugs/bug4030.tsx b/apps/prs/react/src/routes/bugs/bug4030.tsx
new file mode 100644
index 0000000000..f7e88dfe6d
--- /dev/null
+++ b/apps/prs/react/src/routes/bugs/bug4030.tsx
@@ -0,0 +1,20 @@
+import { GoabAppFooter, GoabText } from "@abgov/react-components";
+
+export function Bug4030Route() {
+ return (
+
+
+ Bug #4030: Footer copyright text
+
+
+ The V2 footer should show a single span "© 2026 Government of Alberta". The
+ "This is a Government of Alberta Digital Service" span and the "GoA" abbreviation
+ should both be removed.
+
+
+
+
+ );
+}
+
+export default Bug4030Route;
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 57d9c920d4..31c85558ac 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -71,6 +71,66 @@ The site resolves monorepo packages directly via aliases in `astro.config.mjs`:
---
+## Keeping generated files in sync
+
+The generated files (`generated/component-apis/*.json` and `public/search-index.json`) are committed to git so they show up in PR diffs. The catch: they only refresh when someone runs the build, and most component PRs never touch docs. So the committed files drift out of sync with the Svelte and MDX they come from (see [#3868](https://github.com/GovAlta/ui-components/issues/3868)).
+
+A pre-commit hook keeps them honest.
+
+### What the hook does
+
+When a commit stages a file that feeds a generator, the hook regenerates the outputs and blocks the commit if the committed versions are stale.
+
+- Sources it watches:
+ - `libs/web-components/src/components/**` (Svelte JSDoc, feeds `extract-api`)
+ - `libs/react-components/src/lib/**` (React wrappers, feed `extract-api`)
+ - `libs/angular-components/src/lib/components/**` (Angular wrappers, feed `extract-api`)
+ - `libs/common/src/lib/**` (shared types and imperative-API controllers, feed `extract-api`)
+ - `docs/src/content/**` (MDX frontmatter, feeds `build:search-index`)
+- If none of those are staged, the hook does nothing.
+- If the regenerated output matches what you staged, the commit goes through with no new files to stage. The generators still run on every commit that touches a watched source; they are deterministic, so an already-fresh file comes back byte-identical.
+- If they differ, the commit is blocked, naming the file that drifted and what to run.
+
+### Install it
+
+Running `npm install` at the repo root wires up the hook automatically. A `prepare` script in the root `package.json` points git at the tracked `.githooks/` directory, and a `postinstall` script installs the docs dependencies the generators need (see below):
+
+```json
+"prepare": "git config core.hooksPath .githooks || true"
+```
+
+`prepare` runs at the end of every root `npm install`, so a fresh clone gets the hook after its first install with no extra step. The `|| true` keeps `npm install` from failing where git is not available (CI images, tarball installs).
+
+To set it by hand, or to confirm it is set:
+
+```bash
+git config core.hooksPath .githooks
+```
+
+### Docs dependencies
+
+The generators need the docs dependencies (`tsx`). The docs are a separate npm package, so the root `package.json` has a `postinstall` that runs `npm install --prefix docs`. A single `npm install` at the repo root therefore installs them for everyone, and the hook always has what it needs to run.
+
+If `docs/node_modules` is missing anyway (for example an interrupted install), the hook blocks with a message to run `npm install` at the repo root, rather than letting a possibly-stale file through.
+
+### Bypassing
+
+`git commit --no-verify` skips the hook. Avoid it. A bypassed commit can put a stale file on `dev`, which is the exact problem this prevents. If the hook keeps stopping you, a generated file really is out of date.
+
+### Adding a new generator
+
+To bring a new generator under the same net:
+
+1. Make sure it runs from a single npm script in `docs/` and is deterministic (same input gives byte-identical output, so no timestamps or unsorted file reads).
+2. Add its source path to `SOURCE_REGEX` and its output path to `OUTPUTS` in `.githooks/pre-commit`, and run it in the regenerate step.
+3. Add it to the content sources table above.
+
+### Known limitation
+
+The hook checks the working tree, not the staged snapshot. With partial staging (`git add -p`, or staging a source change while leaving other edits to the same file unstaged), the generators read the working-tree version, which can differ from what you are committing. In that case the hook may stop a commit that is actually consistent. Stage the regenerated output, or commit the rest of your edits, and it clears. This is a deliberate trade-off to keep the hook simple.
+
+---
+
## Content collections
Defined in `src/content/config.ts`. Four collections:
diff --git a/docs/generated/component-apis/temporary-notification.json b/docs/generated/component-apis/temporary-notification.json
index e90e9bb432..1e5e9843d2 100644
--- a/docs/generated/component-apis/temporary-notification.json
+++ b/docs/generated/component-apis/temporary-notification.json
@@ -92,5 +92,92 @@
"events": [],
"slots": []
}
- }
+ },
+ "staticMethods": [
+ {
+ "name": "show",
+ "signature": "TemporaryNotification.show(message, options)",
+ "returnType": "string",
+ "description": "Displays a temporary notification from your component. Returns the notification's UUID, which you can use to dismiss it or update its progress.",
+ "params": [
+ {
+ "name": "message",
+ "type": "string",
+ "required": true,
+ "description": "The message to display in the notification."
+ },
+ {
+ "name": "options.type",
+ "type": "GoabTemporaryNotificationType",
+ "values": [
+ "basic",
+ "success",
+ "failure",
+ "indeterminate",
+ "progress"
+ ],
+ "required": false,
+ "description": "The type of notification, which determines its styling and icon. Use \"indeterminate\" to show an animated progress bar while work of unknown length runs, or \"progress\" to show a progress bar you update with setProgress(). Defaults to \"basic\"."
+ },
+ {
+ "name": "options.duration",
+ "type": "\"long\" | \"medium\" | \"short\" | number",
+ "required": false,
+ "description": "How long the notification stays before it auto-dismisses: \"short\" (about 3 seconds), \"medium\" (about 4 seconds), \"long\" (about 6 seconds), or a number of milliseconds. Only \"basic\", \"success\", and \"failure\" notifications auto-dismiss (default \"short\"). \"indeterminate\" and \"progress\" notifications have no default duration and stay until you dismiss them."
+ },
+ {
+ "name": "options.actionText",
+ "type": "string",
+ "required": false,
+ "description": "Text for an action button. When set, the notification shows a button the user can select."
+ },
+ {
+ "name": "options.action",
+ "type": "() => void",
+ "required": false,
+ "description": "Function to run when the action button is selected."
+ },
+ {
+ "name": "options.cancelUUID",
+ "type": "string",
+ "required": false,
+ "description": "UUID of an existing notification to cancel when this one is shown."
+ }
+ ]
+ },
+ {
+ "name": "dismiss",
+ "signature": "TemporaryNotification.dismiss(uuid)",
+ "returnType": "void",
+ "description": "Hides a notification, using the UUID that show() returns.",
+ "params": [
+ {
+ "name": "uuid",
+ "type": "string",
+ "required": true,
+ "description": "The UUID of the notification to dismiss. This is the value that show() returns."
+ }
+ ]
+ },
+ {
+ "name": "setProgress",
+ "signature": "TemporaryNotification.setProgress(uuid, progress)",
+ "returnType": "void",
+ "description": "Updates the progress shown on a progress notification, using the UUID that show() returns.",
+ "params": [
+ {
+ "name": "uuid",
+ "type": "string",
+ "required": true,
+ "description": "The UUID of the progress notification to update. This is the value that show() returns."
+ },
+ {
+ "name": "progress",
+ "type": "number",
+ "required": true,
+ "description": "The progress to display, from 0 to 100."
+ }
+ ]
+ }
+ ]
}
\ No newline at end of file
diff --git a/docs/package.json b/docs/package.json
index 01203da05f..200ba59b04 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -10,7 +10,9 @@
"extract-api": "npx tsx src/scripts/extract-api.ts --all",
"preview": "astro preview",
"generate-previews": "npx tsx src/scripts/generate-preview-images.ts",
- "astro": "astro"
+ "astro": "astro",
+ "test": "npx tsx --test 'src/scripts/**/*.test.ts'",
+ "verify-bundle": "npx tsx src/scripts/content-generators/verify-bundle.ts"
},
"dependencies": {
"@abgov/design-tokens-v2": "npm:@abgov/design-tokens@^2.8.0",
diff --git a/docs/public/search-index.json b/docs/public/search-index.json
index f5c71b4195..1dbc8888b2 100644
--- a/docs/public/search-index.json
+++ b/docs/public/search-index.json
@@ -2320,6 +2320,42 @@
"aliases": [],
"slug": "workspace/index-page"
},
+ {
+ "type": "page",
+ "id": "ai-tools-and-resources/goa-design-system-mcp",
+ "title": "GoA Design System MCP",
+ "description": "A live lookup tool your AI uses for component APIs, examples, and guidance",
+ "status": "published",
+ "category": "get started",
+ "tags": [
+ "ai-tools-and-resources"
+ ],
+ "slug": "get-started/ai-tools-and-resources/goa-design-system-mcp"
+ },
+ {
+ "type": "page",
+ "id": "ai-tools-and-resources/skills",
+ "title": "Skills",
+ "description": "Plain Markdown instruction files that give your AI a workflow to follow, for building GoA services",
+ "status": "published",
+ "category": "get started",
+ "tags": [
+ "ai-tools-and-resources"
+ ],
+ "slug": "get-started/ai-tools-and-resources/skills"
+ },
+ {
+ "type": "page",
+ "id": "ai-tools-and-resources",
+ "title": "AI tools and resources",
+ "description": "AI tools and resources to help you and your AI work better with the GoA Design System",
+ "status": "published",
+ "category": "get started",
+ "tags": [
+ "ai-tools-and-resources"
+ ],
+ "slug": "get-started/ai-tools-and-resources"
+ },
{
"type": "page",
"id": "automated-accessibility",
@@ -2352,7 +2388,7 @@
"status": "published",
"category": "get started",
"tags": [
- "appendix"
+ "contribute"
],
"slug": "get-started/contribute"
},
@@ -2531,7 +2567,7 @@
"status": "published",
"category": "get started",
"tags": [
- "appendix"
+ "out-of-support"
],
"slug": "get-started/out-of-support"
},
@@ -2543,7 +2579,7 @@
"status": "published",
"category": "get started",
"tags": [
- "appendix"
+ "qa-testing"
],
"slug": "get-started/qa-testing"
},
diff --git a/docs/src/components/Breadcrumbs.astro b/docs/src/components/Breadcrumbs.astro
index 0d668720f9..7ae3e28be6 100644
--- a/docs/src/components/Breadcrumbs.astro
+++ b/docs/src/components/Breadcrumbs.astro
@@ -55,6 +55,7 @@ const SEGMENT_LABELS: Record = {
"examples": "Examples",
"designers": "Designers",
"developers": "Developers",
+ "ai-tools-and-resources": "AI Tools and Resources",
};
function segmentToLabel(segment: string): string {
diff --git a/docs/src/components/CodeSnippet.css b/docs/src/components/CodeSnippet.css
index d8916fd894..93aca2a9b3 100644
--- a/docs/src/components/CodeSnippet.css
+++ b/docs/src/components/CodeSnippet.css
@@ -124,10 +124,12 @@
mask-image: linear-gradient(to bottom, black 0%, black 60%, transparent 100%);
}
-pre {
+.code-snippet .code-block .code-container pre,
+.framework-switcher .code-block .code-container pre {
margin: 0;
- padding: var(--goa-space-m, 1rem);
- overflow-x: auto;
+ padding: var(--goa-space-s, 0.75rem) var(--goa-space-m, 1rem);
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
}
code {
diff --git a/docs/src/components/SiteNav.tsx b/docs/src/components/SiteNav.tsx
index cd89579beb..8959250e0b 100644
--- a/docs/src/components/SiteNav.tsx
+++ b/docs/src/components/SiteNav.tsx
@@ -94,9 +94,7 @@ function getInitialMenuState(): boolean {
}
const EMPTY_GET_STARTED_NAV: GetStartedNav = {
- topPages: [],
- groups: [],
- bottomPages: [],
+ sections: [],
};
export function SiteNav({
diff --git a/docs/src/components/StaticMethods.astro b/docs/src/components/StaticMethods.astro
new file mode 100644
index 0000000000..488ea852bf
--- /dev/null
+++ b/docs/src/components/StaticMethods.astro
@@ -0,0 +1,154 @@
+---
+/**
+ * StaticMethods - Documents imperative helper methods (e.g. TemporaryNotification.show)
+ * that are exposed as a static API rather than element props.
+ *
+ * Framework-agnostic: a single signature snippet plus a params table, rendered the
+ * same way regardless of the selected framework. Visual language matches PropsTable.
+ */
+import type { StaticMethod } from "../lib/content-queries";
+
+interface Props {
+ methods: StaticMethod[];
+}
+
+const { methods } = Astro.props;
+---
+
+{
+ methods.map((method) => (
+
+
+
{method.name}()
+
+
+ {method.description &&
{method.description}
}
+
+
{method.signature}{method.returnType ? `: ${method.returnType}` : ""}
+
+
+
+ {method.params.map((param) => (
+
+
+ {param.name}
+ {param.required && (
+
+ )}
+
+
+ {param.type}
+ {param.values && param.values.length > 0 && (
+ {param.values.join(" | ")}
+ )}
+
+ {param.description && (
+
+ {param.description}
+
+ )}
+
+ ))}
+
+
+
+ ))
+}
+
+
diff --git a/docs/src/components/nav/GetStartedSubMenu.tsx b/docs/src/components/nav/GetStartedSubMenu.tsx
index 1ae70ad26d..8faea4c8bb 100644
--- a/docs/src/components/nav/GetStartedSubMenu.tsx
+++ b/docs/src/components/nav/GetStartedSubMenu.tsx
@@ -1,14 +1,13 @@
/**
* GetStartedSubMenu.tsx
*
- * Sub-menu for Get Started section showing grouped pages.
- * Uses GoabWorkSideMenuGroup for expandable Designers/Developers sections.
- *
- * Nav structure is sourced from the get-started content collection via
+ * Sub-menu for Get Started section showing pages organized into sections.
+ * Each section is either flat (a list of items) or grouped (items inside an
+ * expandable group with a heading). Sections render in the order returned by
* `getGetStartedNav()` in lib/get-started-nav.ts.
*/
-import { type MouseEvent } from "react";
+import { Fragment, type MouseEvent } from "react";
import {
GoabWorkSideMenu,
GoabWorkSideMenuItem,
@@ -16,7 +15,7 @@ import {
} from "@abgov/react-components";
import { MenuSecondaryContent } from "./MenuSecondaryContent";
import { withBase } from "@/lib/base-url";
-import type { GetStartedNav } from "@/lib/get-started-nav";
+import type { GetStartedNav, GetStartedNavSection } from "@/lib/get-started-nav";
interface GetStartedSubMenuProps {
isOpen: boolean;
@@ -41,6 +40,36 @@ export function GetStartedSubMenu({
onBack();
};
+ const renderSection = (section: GetStartedNavSection) => {
+ if (section.type === "flat") {
+ return (
+
+ {section.pages.map((page) => (
+
+ ))}
+
+ );
+ }
+
+ const containsCurrentPage = section.pages.some((p) => p.url === currentUrl);
+
+ const handleGroupClickCapture = () => {
+ if (!isOpen && onExpandMenu) {
+ onExpandMenu();
+ }
+ };
+
+ return (
+
+
+ {section.pages.map((page) => (
+
+ ))}
+
+
+ );
+ };
+
const primaryContent = (
<>
{/* Back to parent menu */}
@@ -48,50 +77,7 @@ export function GetStartedSubMenu({
- {/* Top-level pages */}
- {items.topPages.map((page) => (
-
- ))}
-
- {/* Grouped sections */}
-
- {items.groups.map((group) => {
- const containsCurrentPage = group.pages.some((p) => p.url === currentUrl);
-
- const handleGroupClickCapture = () => {
- if (!isOpen && onExpandMenu) {
- onExpandMenu();
- }
- };
-
- return (
-
-
- {group.pages.map((page) => (
-
- ))}
-
-
- );
- })}
-
-
- {/* Bottom pages */}
- {items.bottomPages.map((page) => (
-
- ))}
+ {items.sections.map(renderSection)}
>
);
diff --git a/docs/src/content/config.ts b/docs/src/content/config.ts
index 24c1a69adc..c457369e3b 100644
--- a/docs/src/content/config.ts
+++ b/docs/src/content/config.ts
@@ -267,7 +267,15 @@ const getStarted = defineCollection({
description: z.string().optional(),
// Submenu placement. "intro" and "appendix" are top-level (above and below
// the grouped sections); "designers" and "developers" are grouped.
- section: z.enum(["intro", "designers", "developers", "appendix"]),
+ section: z.enum([
+ "intro",
+ "designers",
+ "developers",
+ "qa-testing",
+ "ai-tools-and-resources",
+ "contribute",
+ "out-of-support",
+ ]),
// Sort order within section.
order: z.number(),
status: z.enum(["published", "draft", "deprecated"]).default("published"),
diff --git a/docs/src/content/get-started/ai-tools-and-resources.mdx b/docs/src/content/get-started/ai-tools-and-resources.mdx
new file mode 100644
index 0000000000..1a380972dc
--- /dev/null
+++ b/docs/src/content/get-started/ai-tools-and-resources.mdx
@@ -0,0 +1,75 @@
+---
+id: ai-tools-and-resources
+title: AI tools and resources
+navLabel: Overview
+description: AI tools and resources to help you and your AI work better with the GoA Design System
+section: ai-tools-and-resources
+order: 1
+status: published
+---
+import DropInCallout from "../../components/DropInCallout.astro";
+import { withBase } from "@/lib/base-url";
+
+AI tools and resources
+These tools help you and your AI work with the GoA Design System. Each tool serves a different purpose, so use them together depending on the task.
+
+AI toolset
+
+
+
+
+
+ | Tool |
+ How it works |
+ What it gives your AI |
+
+
+
+
+ | GoA Design System MCP |
+ Lookup tool. Your AI calls it on demand. |
+ Component APIs, examples, and guidance, like asking a reference librarian |
+
+
+ | Skills |
+ Instructions that load when the work matches. |
+ Layered instructions for using the Design System in your work, like a playbook for the work at hand |
+
+
+ | MD files |
+ Static reference. Pasted upfront, stays in your AI's session. |
+ The full Design System reference, like a textbook on the desk |
+
+
+ | Figma MCP |
+ Lookup tool. Your AI calls it on demand. |
+ Read a design in Figma to get the components, variables, and specs, like lifting measurements off a blueprint |
+
+
+
+
+
+
+ All tools read from the same content this site renders, so answers stay consistent no matter which one you use.
+
+
+GoA Design System MCP
+Your AI pulls live Design System knowledge as it builds, so it uses the right components, the right props, and real examples instead of guessing.
+View more
+
+Skills
+Plain Markdown instruction files give your AI a workflow to follow when the work matches, so it builds a GoA service with the right structure, templates, and patterns from the start.
+View more
+
+
+ MD files
+
+
+Useful when you want to give your AI broad Design System context, include the reference in a project, or reach for the content offline.
+How to access it: Per-framework downloadable bundles (React, Angular, Web Components).
+
+Figma MCP
+Built and maintained by Figma. Pair it with the GoA Design System MCP when work spans design and code, so your AI can bridge Figma designs to real coded components from the Design System.
+How to access it: Figma's guide to the MCP server
+
+
diff --git a/docs/src/content/get-started/ai-tools-and-resources/goa-design-system-mcp.mdx b/docs/src/content/get-started/ai-tools-and-resources/goa-design-system-mcp.mdx
new file mode 100644
index 0000000000..f05e477c8e
--- /dev/null
+++ b/docs/src/content/get-started/ai-tools-and-resources/goa-design-system-mcp.mdx
@@ -0,0 +1,253 @@
+---
+id: ai-tools-and-resources/goa-design-system-mcp
+title: GoA Design System MCP
+navLabel: Design System MCP
+description: A live lookup tool your AI uses for component APIs, examples, and guidance
+section: ai-tools-and-resources
+order: 2
+status: published
+---
+import DropInCallout from "../../../components/DropInCallout.astro";
+import { CodeSnippet } from "../../../components/CodeSnippet";
+import { CodeCopy } from "../../../components/CodeCopy";
+import { withBase } from "@/lib/base-url";
+
+GoA Design System MCP
+The live lookup tool your AI calls when it needs Design System component APIs, examples, or guidance.
+
+
+
+
+View tool-specific setup
+
+
+ The MCP is one tool in a larger AI toolset. For the overview of all available AI tools and how they fit together, see AI tools and resources.
+
+
+What's included
+
+Everything the MCP returns comes from the same content this site renders.
+
+
+
+
+
+ | Type |
+ What's in it |
+
+
+
+
+ | Components |
+ The full GoA component library with props, variants, and accessibility notes. React, Angular, and Web Components. |
+
+
+ | Examples |
+ Workspace and Public form product types, page patterns, section patterns, and smaller task examples. |
+
+
+ | Guidance |
+ Do's, don'ts, and tips that apply across components. Accessibility grounded in WCAG 2.2 AA. |
+
+
+
+
+
+What can you ask?
+
+Ask your AI in plain language. It calls the MCP when a component, example, or guidance answer fits the request.
+
+
+
+
+
+ | About a component |
+
+
+
+
+ |
+
+ "What props does the Table component have?"
+
+
+ |
+
+
+ |
+
+ "How does FormItem handle error states?"
+
+
+ |
+
+
+ |
+
+ "Which GoA component should I use for a card layout?"
+
+
+ |
+
+
+
+
+
+
+
+
+
+ | For an example or pattern |
+
+
+
+
+ |
+
+ "Find dashboard examples in the workspace product type"
+
+
+ |
+
+
+ |
+
+ "Show me React examples for a question page"
+
+
+ |
+
+
+ |
+
+ "What's a good example of a filter bar?"
+
+
+ |
+
+
+
+
+
+
+
+
+
+ | For guidance, do's, and don'ts |
+
+
+
+
+ |
+
+ "What's the guidance for labelling buttons?"
+
+
+ |
+
+
+ |
+
+ "Show me the do's and don'ts for dropdowns"
+
+
+ |
+
+
+ |
+
+ "What accessibility guidance applies to forms?"
+
+
+ |
+
+
+
+
+
+Setup
+
+Find your AI tool below to connect it. There's nothing to install.
+
+
+ To verify your setup, restart your AI tool and ask "Look up the GoA Button component." You should get a response covering Button's props, variants, and usage.
+
+
+Claude Code (CLI)
+
+View Claude Code's MCP documentation
+
+The fastest path is the claude mcp add command:
+
+
+
+Claude Desktop
+
+View Claude Desktop's MCP setup guide
+
+Claude Desktop only supports local (stdio) MCP servers, so connect through the mcp-remote bridge. Edit the config file at:
+
+
+ - Mac:
~/Library/Application Support/Claude/claude_desktop_config.json
+ - Windows:
%APPDATA%\Claude\claude_desktop_config.json
+
+
+
+
+Cursor
+
+View Cursor's MCP documentation
+
+Cursor supports remote HTTP MCP servers directly. Open Settings > Tools & MCP to add a server, or edit ~/.cursor/mcp.json (global) or .cursor/mcp.json in your project:
+
+
+
+ChatGPT
+
+View ChatGPT's MCP connectors guide
+
+Custom apps are in beta and require a ChatGPT Plus or Pro subscription.
+
+
+ - In ChatGPT, open Settings > Apps > Advanced settings and turn on Developer mode.
+ - Select Add app. A New App form opens.
+ - Enter a Name (for example, "GoA Design System").
+ - Under Connection, select Server URL and paste the MCP endpoint URL:
+ - Set Authentication to None.
+ - Check I understand and want to continue to acknowledge the risk warning.
+ - Save.
+
+
+
+ Using a different AI tool? If it supports remote MCP servers, paste the MCP endpoint URL into its config. For stdio-only tools, bridge through npx mcp-remote <URL>. View the list of MCP-compatible clients
+
+
+
diff --git a/docs/src/content/get-started/ai-tools-and-resources/skills.mdx b/docs/src/content/get-started/ai-tools-and-resources/skills.mdx
new file mode 100644
index 0000000000..f9fde561da
--- /dev/null
+++ b/docs/src/content/get-started/ai-tools-and-resources/skills.mdx
@@ -0,0 +1,50 @@
+---
+id: ai-tools-and-resources/skills
+title: Skills
+navLabel: Skills
+description: Plain Markdown instruction files that give your AI a workflow to follow, for building GoA services
+section: ai-tools-and-resources
+order: 3
+status: published
+---
+import DropInCallout from "../../../components/DropInCallout.astro";
+import { CodeSnippet } from "../../../components/CodeSnippet";
+import { withBase } from "@/lib/base-url";
+
+Skills
+Plain Markdown instruction files give your AI a workflow to follow when the work matches. They follow the open SKILL.md standard, so the same files work across Claude Code, Cursor, Copilot, and more.
+
+
+ Skills are one tool in a larger AI toolset. For the overview of all available AI tools and how they fit together, see AI tools and resources.
+
+
+Available skills
+
+using-goa-design-system
+This skill identifies the right product type, page and section templates, and components for what you're building. Describe what you're working on in plain language (a public form or a case-management tool, for example), and it pulls the relevant structure and live component specs from the MCP.
+It loads when you're scoping or building a screen, page, or feature, so your AI follows the Design System's structure from the start rather than guessing.
+How to add it:
+
+View on GitHub
+
+content-design
+This skill writes user-facing copy tailored to its reader (a citizen or a worker) because the same message requires a different approach for each audience.
+It loads when you're working on the words in a service: labels, guidance, errors, empty states, and notifications.
+How to add it:
+
+View on GitHub
+
+How they work
+Each skill is a folder with a SKILL.md file plus any supporting notes. Your AI reads the short description at startup and loads the full instructions only when your request matches, so they add capability without crowding the context.
+Because they follow the open Agent Skills standard, the same files work in Claude Code, Cursor, GitHub Copilot, and other tools that read the format.
+
+Updates
+Skills track the dev branch of the GoA Design System repository. To keep them current, run:
+
+To update automatically, add npx skills update -g -y to a SessionStart hook in your AI tool and they refresh every session. You'll only get an update when a skill itself changes, not every time something else in the repo changes.
+
+
+ Using a tool without the skills CLI? Clone a skill folder from the skills directory into your tool's skills folder, for example ~/.claude/skills, .cursor/skills, or .github/skills.
+
+
+
diff --git a/docs/src/content/get-started/contribute.mdx b/docs/src/content/get-started/contribute.mdx
index 8e6f4e0437..ea77f0e1e9 100644
--- a/docs/src/content/get-started/contribute.mdx
+++ b/docs/src/content/get-started/contribute.mdx
@@ -2,8 +2,8 @@
id: contribute
title: Contribute
description: How to contribute to the GoA Design System
-section: appendix
-order: 2
+section: contribute
+order: 1
status: published
---
import { withBase } from "@/lib/base-url";
diff --git a/docs/src/content/get-started/out-of-support.mdx b/docs/src/content/get-started/out-of-support.mdx
index fa61fea200..ab9e254813 100644
--- a/docs/src/content/get-started/out-of-support.mdx
+++ b/docs/src/content/get-started/out-of-support.mdx
@@ -2,8 +2,8 @@
id: out-of-support
title: Out of support versions
description: Design System versions that are no longer supported
-section: appendix
-order: 3
+section: out-of-support
+order: 1
status: published
---
import { withBase } from "@/lib/base-url";
diff --git a/docs/src/content/get-started/qa-testing.mdx b/docs/src/content/get-started/qa-testing.mdx
index edb2d726e1..df80b892a3 100644
--- a/docs/src/content/get-started/qa-testing.mdx
+++ b/docs/src/content/get-started/qa-testing.mdx
@@ -2,7 +2,7 @@
id: qa-testing
title: QA testing
description: Testing process for Design System components
-section: appendix
+section: qa-testing
order: 1
status: published
---
diff --git a/docs/src/content/get-started/roadmap.mdx b/docs/src/content/get-started/roadmap.mdx
index 0672ebdd37..f7743071f5 100644
--- a/docs/src/content/get-started/roadmap.mdx
+++ b/docs/src/content/get-started/roadmap.mdx
@@ -16,12 +16,12 @@ status: published
-Now
-Focus: Drive DS 2.0 adoption momentum, protect post-launch stability, and prove DS 2.0 helps teams move faster through prescriptive guidance and examples.
+Q1 (April - June)
+Focus: Drive design system adoption momentum, enable AI-assisted workflows through the design system MCP, and make an informed decision on Vue support.
-DS 2.0 adoption and satisfaction measurement
-Objective: Establish reliable DS 2.0 adoption tracking and reporting, and pair it with satisfaction signals from designers, developers, and end users to understand whether DS 2.0 is improving service outcomes as teams upgrade.
-Benefit: Enables accurate progress reporting and provides evidence that DS 2.0 is improving usability, accessibility, and experience quality, not only adoption.
+Design system adoption and satisfaction measurement
+Objective: Establish reliable design system adoption tracking and reporting, and pair it with satisfaction signals from designers, developers, and end users to understand whether the updated design system is improving service outcomes as teams upgrade.
+Benefit: Enables accurate progress reporting and provides evidence that the updated design system is improving usability, accessibility, and experience quality, not only adoption.
Examples:
- Create a mechanism to reliably collect and report adoption rate
@@ -31,16 +31,26 @@ status: published
Product acceleration enablement
-Objective: Provide prescriptive guidance and resources that help teams get to screens, prototypes, and working experiences faster using DS 2.0.
+Objective: Provide prescriptive guidance and resources that help teams get to screens, prototypes, and working experiences faster using the updated design system.
Benefit: Reduces time to first screens and improves consistency by helping teams start from proven patterns instead of building from scratch.
Examples:
- - Create resources that show how to quickly prototype with DS 2.0 components and examples
+ - Create resources that show how to quickly prototype with design system components and examples
- Provide contextual starting points for teams using Public form and Workspace templates
- - Pilot DS 2.0 with teams to generate strong examples to share
+ - Pilot the updated design system with teams to generate strong examples to share
- Create and publish migration success stories
+Vue support decision
+Objective: Explore and define the minimum work required to confidently state the design system officially supports Vue, including scope, constraints, and support model.
+Benefit: Prevents unclear commitments and reduces strategic risk by enabling an informed investment decision aligned to organizational needs.
+Examples:
+
+ - Define what "official Vue support" means
+ - Outline the minimum supportable scope
+ - Identify implications for documentation, maintenance, testing, and support
+
+
Design system MCP
Objective: Stand up the design system Model Context Protocol (MCP) capability so AI tools and platforms can reliably access design system guidance, components, patterns, and standards.
Benefit: Helps teams generate more consistent, standards-aligned front-ends by making trusted design system context available directly in AI-assisted workflows.
@@ -52,31 +62,49 @@ status: published
Document recommended workflows for teams
-Vue support decision
-Objective: Explore and define the minimum work required to confidently state DS 2.0 officially supports Vue, including scope, constraints, and support model.
-Benefit: Prevents unclear commitments and reduces strategic risk by enabling an informed investment decision aligned to organizational needs.
+
+
+Q2 (July - September)
+Focus: Improve the core Public form and Workspace experience and execute on the chosen Vue direction.
+
+
+Objective: Build on the existing public form offering to make it faster to implement and easier to use, while preserving the flexibility product teams need.
+Benefit: Unlocks downstream public form capability work and reduces the effort required for teams to build consistent, high-quality public-facing services.
Examples:
- - Define what "official Vue support" means
- - Outline the minimum supportable scope
- - Identify implications for documentation, maintenance, testing, and support
+ - Identify gaps and opportunities in the current public form offering
+ - Determine what needs to be built, adapted, or improved to better support the public form use case
+ - Deliver improvements so teams can build on a more complete and supportable foundation
-
-
-Next
-Focus: Improve the core Public form and Workspace experience, mature reusable patterns and examples, reduce adoption friction through better packaging, and execute on the chosen Vue direction.
-
Public form and Workspace capability improvements
Objective: Enhance key Public form and Workspace capabilities based on adoption needs and workflow requirements.
-Benefit: Improves feature sets and reduces the need for custom one-off solutions by making common workflows easier to implement with DS 2.0.
-Example:
+Benefit: Improves feature sets and reduces the need for custom one-off solutions by making common workflows easier to implement with the updated design system.
+Examples:
- Build the review, revise, resubmit feature
+ - Navigation pattern for multi-step Workspace workflows
+ - Comments component for Workspace
+ - Combined Public form and Workspace playground
+Vue follow-through
+Objective: Based on Vue discovery, either implement formal Vue support or clearly position Angular and React as the recommended approach for teams using AI-assisted workflows.
+Benefit: Provides clear direction to product teams and reduces uncertainty, enabling progress on a supported path while keeping support sustainable for the design system team.
+Examples:
+
+ - Publish Vue support guidance and expectations
+ - Implement agreed scope if approved
+ - Publish recommended alternatives if Vue support is not pursued
+
+
+
+
+Q3 (October - December)
+Focus: Mature reusable patterns and examples, and reduce adoption friction through better library packaging and tech stack upgrades.
+
Examples and pattern maturity
-Objective: Expand and refine examples and position them as adaptable reusable patterns that teams can apply with confidence.
+Objective: Expand and refine examples and position them as adaptable reusable assets that teams can apply with confidence.
Benefit: Improves self-serve success and product consistency, reducing implementation variance and support demand over time.
Examples:
@@ -93,20 +121,10 @@ status: published
- Update to Svelte 5
-Vue follow-through
-Objective: Based on Vue discovery, either implement formal Vue support or clearly position Angular and React as the recommended approach for teams using AI-assisted workflows.
-Benefit: Provides clear direction to product teams and reduces uncertainty, enabling progress on a supported path while keeping support sustainable for the design system team.
-Examples:
-
- - Publish Vue support guidance and expectations
- - Implement agreed scope if approved
- - Publish recommended alternatives if Vue support is not pursued
-
-
-Later
-Focus: Support DS 2.0 version updates and improve documentation and communications quality to support scaling and sustainability.
+Q4 (January - March)
+Focus: Support design system version updates and improve documentation and communications quality to support scaling and sustainability.
Documentation and communications improvements
Objective: Improve how teams access and understand design system guidance, and communicate design system value areas like accessibility more clearly.
@@ -117,8 +135,8 @@ status: published
Articulate accessibility work more clearly (what is covered and how it supports teams)
-Targeted capacity support for DS 2.0 version updates
-Objective: Provide targeted design system team capacity to help remaining DS 1.x product teams complete the DS 2.0 version update.
+Targeted capacity support for design system version updates
+Objective: Provide targeted design system team capacity to help remaining product teams complete the design system version update.
Benefit: Accelerates adoption progress and unblocks teams facing more difficult migrations, supporting adoption targets while keeping support sustainable through a focus on teams most in need.
Example:
diff --git a/docs/src/layouts/BaseLayout.astro b/docs/src/layouts/BaseLayout.astro
index 0f9905158d..aaf0b4fe02 100644
--- a/docs/src/layouts/BaseLayout.astro
+++ b/docs/src/layouts/BaseLayout.astro
@@ -107,12 +107,24 @@ const ogUrl = new URL(withBase(Astro.url.pathname), Astro.site).href;
html {
color-scheme: light;
+ /* Match the page's grey wrapper so the scroll overflow / overscroll
+ area (visible on elastic scroll) blends instead of showing white.
+ Same token as .layout-wrapper, so it matches in light/dark/forced-dark. */
+ background: var(--goa-color-greyscale-50);
}
html[data-theme="dark"] {
color-scheme: dark;
}
+ /* Below 624px the layouts flip the page wrapper to white, so match the
+ overscroll area there too instead of leaving it grey. */
+ @media (max-width: 623px) {
+ html {
+ background: var(--goa-color-greyscale-white);
+ }
+ }
+
body {
font: var(--goa-typography-body-m);
color: var(--goa-color-text-default);
diff --git a/docs/src/layouts/PreviewLayout.astro b/docs/src/layouts/PreviewLayout.astro
index 112c4a3468..f6b2a5f064 100644
--- a/docs/src/layouts/PreviewLayout.astro
+++ b/docs/src/layouts/PreviewLayout.astro
@@ -48,7 +48,7 @@ const resolvedBackUrl = withBase(backUrl);
-
+