Xzero UI
Pro library

Domain verifyPro

A custom domain setup card where customers add a domain and copy the DNS records. A live checker then polls resolvers, marks each record valid, not found or misconfigured with a one-line fix, and finishes with the domain verified and the SSL certificate issued.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/domain-verify
Live · keyboard ready

Drop it into a project's domain settings and connect three async handlers to your backend. getRecords(domain, signal) returns the records the customer must create. verify(domain, records, { signal, attempt, run }) runs one check and returns per-record status plus how many regions have propagated. issueCertificate(domain, signal) returns { issuer, renews }. The card does the polling (pollInterval), aborts requests when the domain is removed or the component unmounts, and stops on "all valid" or on any "invalid" record. On an invalid record it shows found and fix under that row. Pass provider (name + mark) from your NS lookup, or null when you don't know it. Listen with onVerified, onRemove and onPhaseChange. With no props it plays a simulated flow until someone clicks or types inside it.

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/domain-verify

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { DomainVerify } from "@/components/xzero/blocks/domain-verify/domain-verify";

export function DomainSettings({ projectId }: { projectId: string }) {
  return <DomainVerify
    defaultDomain=""
    provider={null}
    getRecords={(domain, signal) => fetch(`/api/projects/${projectId}/domains`, { method: "POST", body: JSON.stringify({ domain }), signal }).then(r => r.json())}
    verify={(domain, _records, { signal }) => fetch(`/api/projects/${projectId}/domains/${domain}/verify`, { signal }).then(r => r.json())}
    issueCertificate={(domain, signal) => fetch(`/api/projects/${projectId}/domains/${domain}/certificate`, { method: "POST", signal }).then(r => r.json())}
    onVerified={domain => console.log(`${domain} is live`)}
  />;
}

API reference

DomainVerify

Custom domain setup in one card. The customer enters a domain, copies the DNS records (type, name, value with copy buttons), and a live checker polls resolvers: each record shows not checked, checking, valid, not found or misconfigured with a one line fix, the footer reports propagation across regions, and the card finishes with the domain verified and the SSL certificate issued. With no props it plays a simulated flow, a Cloudflare proxied CNAME that gets fixed, and no DNS is queried.

PropTypeDefaultDescription
defaultDomainstring"app.acme.com"Prefilled domain. Pasted protocols, paths, ports and trailing dots are stripped before validation.
titlestring"Custom domain"Card title.
descriptionstring"Serve your workspace from a domain you own."One muted line under the title.
provider{ name: string; mark?: ReactNode } | nullCloudflareDetected DNS host, shown as "DNS hosted at {name}" with a 14px mark. Pass null to hide it.
getRecords(domain: string, signal: AbortSignal) => Promise<DnsRecord[]>–Returns the records to create. Each record is { id, type, name, value, purpose? }. Defaults to a CNAME (an A record on an apex), an ownership TXT and a CAA for Let's Encrypt.
verify(domain: string, records: DnsRecord[], context: { signal: AbortSignal; attempt: number; run: number }) => Promise<VerifyResult>–One DNS check. Return { records: RecordCheck[], regions: { propagated, total } }. A RecordCheck is { id, status: "valid" | "missing" | "invalid", detail?, found?, fix? }. The card polls while any record is missing, and stops on all valid or any invalid, showing found and fix under that row.
issueCertificate(domain: string, signal: AbortSignal) => Promise<{ issuer: string; renews: string }>–Called once every record is valid. The domain row shows "Issuing SSL certificate" until it resolves.
pollIntervalnumber800 simulated, 5000 with verifyMilliseconds between checks.
autoPlaybooleantrue when no handlers are passedPlays add, verify, fix and verified by itself, pausing off screen. Any pointer or key press inside the card hands control to the person.
onPhaseChange(phase: "enter" | "adding" | "records" | "checking" | "invalid" | "verified") => void–Called on every phase change.
onVerified(domain: string, certificate: Certificate) => void–Called after the certificate is issued.
onRemove(domain: string) => void–Called when the domain is removed with the close button or when Add another starts over.
classNamestring–Extra class on the card.

Keyboard interactions

KeysAction
EnterIn the domain field, adds the domain (same as the Add domain button).
TabMoves through the remove button, each record's copy button and the footer action.
Space / EnterActivates the focused copy, remove or footer button.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Guidance for AI tools

Blocks are complete, self-contained screens with sample data. Replace the sample data and connect the callbacks described above. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components