Build a verification page
Design and implement a frontend verification experience. Covers UX states, component mapping, accessibility, and visual language for verification results.
Use case
You are building a public-facing page where anyone can verify a document. They enter a reference code, scan a barcode, or upload a file — and your page shows the verification result.
This is one of the most important user-facing surfaces in any Ananke Labs integration. Getting the UX right builds trust in the verification process itself.
UX states
Your verification page must handle five distinct states:
| State | Trigger | Visual |
|---|---|---|
| Idle | Page load, no input yet | Input form with clear instructions |
| Loading | Request in flight | Spinner or skeleton, disable inputs |
| Verified (valid) | verdict: "valid" | Green success with document details |
| Warning (suspended/expired) | verdict: "suspended" or "expired" | Amber warning with explanation |
| Error (revoked/not found/network) | verdict: "revoked", "not_found", or fetch error | Red error or neutral "not found" |
Loading state
function VerificationLoading() {
return (
<div className="animate-pulse rounded-lg border p-6">
<div className="h-6 w-48 rounded bg-neutral-200" />
<div className="mt-4 space-y-2">
<div className="h-4 w-full rounded bg-neutral-100" />
<div className="h-4 w-3/4 rounded bg-neutral-100" />
</div>
</div>
);
}Never show a cached or stale result during loading. Always clear the previous result and show the loading state.
Verified state
When verdict is "valid", show:
- A green success indicator (icon + background)
- Issuer name and logo (if available)
- Recipient name
- Document title and type
- Issue date
- Document reference
function VerifiedResult({ result }) {
return (
<div className="rounded-lg border-2 border-green-200 bg-green-50 p-6">
<div className="flex items-center gap-3">
<CheckCircleIcon className="h-8 w-8 text-green-600" />
<div>
<h2 className="text-lg font-semibold text-green-900">
Document Verified
</h2>
<p className="text-sm text-green-700">
This document is authentic and currently valid.
</p>
</div>
</div>
<dl className="mt-6 grid grid-cols-2 gap-4 text-sm">
<div>
<dt className="font-medium text-neutral-500">Issuer</dt>
<dd className="text-neutral-900">{result.issuerName}</dd>
</div>
<div>
<dt className="font-medium text-neutral-500">Recipient</dt>
<dd className="text-neutral-900">{result.recipientName}</dd>
</div>
<div>
<dt className="font-medium text-neutral-500">Document</dt>
<dd className="text-neutral-900">{result.documentTitle}</dd>
</div>
<div>
<dt className="font-medium text-neutral-500">Issued</dt>
<dd className="text-neutral-900">
{new Date(result.issuedAt).toLocaleDateString()}
</dd>
</div>
</dl>
</div>
);
}Error states
Different errors need different visual treatments:
- Revoked — Red error state. Clearly state the document was permanently invalidated. Include revocation reason if available.
- Suspended — Amber warning. Explain this is temporary and the document may be reinstated.
- Not found — Neutral grey state. Do NOT say "invalid" — the reference may just be mistyped. Suggest checking the input.
- Network error — Show a retry option. Never display "invalid" when the real issue is a failed network request.
Component mapping
Map API fields to UI components:
| API field | UI component |
|---|---|
verdict | Status indicator (icon + colour + label) |
issuerName | Issuer section with optional logo |
recipientName | Recipient name display |
documentTitle | Document title heading |
documentType | Type badge or label |
issuedAt | Formatted date |
reference | Monospace reference code |
Accessibility
- Use ARIA live regions for dynamic result updates
- Don't rely on colour alone — include text labels and icons
- Make the input form keyboard-navigable
- Provide meaningful alt text for status icons
- Ensure sufficient contrast ratios (4.5:1 minimum)
- Announce results to screen readers when they appear
Ananke Trust verification page
"use client";
import { useState } from "react";
import { AnankeClient } from "@ananke/sdk";
const client = new AnankeClient({ baseUrl: "/api/proxy" });
export default function TrustVerifyPage() {
const [reference, setReference] = useState("");
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
async function handleVerify(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setResult(null);
try {
const res = await client.trust.verify.byReference(reference);
setResult(res);
} catch (err) {
setResult({ verdict: "error", message: "Verification failed" });
} finally {
setLoading(false);
}
}
return (
<form onSubmit={handleVerify}>
<input
value={reference}
onChange={(e) => setReference(e.target.value)}
placeholder="Enter document reference (TRF-...)"
/>
<button type="submit" disabled={loading}>
{loading ? "Verifying..." : "Verify"}
</button>
{result && <VerificationResult result={result} />}
</form>
);
}Ananke TCode scan page
For Ananke TCode, the verification page typically includes a barcode scannerusing the device camera. The scan decodes the DataMatrix payload and sends it to the verify endpoint.
// After the barcode scanner decodes the DataMatrix:
async function onBarcodeScan(payload: string) {
const result = await client.tcode.verify.byPayload({ payload });
// Display the result using the same component mapping
}Next steps
- Handle errors — Comprehensive error handling patterns.
- Ananke Trust verification — Full verification reference.
- Ananke TCode verification — Scan and verify reference.