Skip to content

Patterns · Technical infrastructure

CustomDomainConnect

Connect a custom domain: enter it, copy the DNS records, verify, and see pending, verifying, active or failed.

@rookcom/monarch-react/patternsPattern

Full flow (simulated checks)

The address visitors will type, such as help.example.com.

Usage examples

Pending, with records and provider hints

Failed with a reason

Active

Purpose

One flow for every product that serves a page on the customer’s own domain (support pages, booking pages, status pages, careers pages). It handles the steps, syntax checking, copyable records and status; the product runs the DNS checks. Import from @rookcom/monarch-react/patterns.

Use when

  • A customer points their own domain or subdomain at a page the product hosts.
  • The product gives DNS records to add and can check them on request or on a timer.

Avoid when

  • Showing a single value to copy, such as an API key or webhook URL: use CopyField or SecretField.
  • Picking a sub-path or slug on the product’s own domain: use an Input with a prefix.

Design principles

  • Four plain steps (Domain, DNS records, Verify, Live) shown with Stepper, so people know how far along they are.
  • Mistakes are caught before the network: what people paste (https://, paths, ports, capitals, a trailing dot) is cleaned up, and anything still not a host name is explained.
  • Every record value is a CopyField, so nothing is retyped by hand.
  • No provider copy is built in. Menus at DNS providers change, so the product passes provider hints it maintains.
  • Status is text and a badge, never colour alone; the failure reason is the product’s own words.

API reference

import { CustomDomainConnect, domainSyntaxError, normalizeDomain } from '@rookcom/monarch-react/patterns';
ExportKindRenderingRoot class
CustomDomainConnectcomponentclient.mn-custom-domain-connect
domainSyntaxErrorutilityclient–
normalizeDomainutilityclient–

Renders a <section> (ref) with data-step set to enter, pending, verifying, active or failed.

onConnect(domain) receives the normalised domain; reject with an Error to show its message under the field. onVerify() and onRemove() may return promises.

domain / defaultDomain / onDomainChange hold the connected domain (null before connecting and after removal). status comes from the product.

normalizeDomain(input) and domainSyntaxError(input, { requireSubdomain }) are exported for server-side reuse.

Generated from the TypeScript declarations of packages/react/src/patterns/technical/custom-domain-connect.

Properties

CustomDomainConnect

Connects a custom domain in four steps: enter the domain (syntax checked in the browser), copy the DNS records, verify, and see the result: pending, verifying, active, or failed with a reason. The product runs every DNS check through `onConnect`, `onVerify` and `onRemove`, and supplies any provider-specific instructions.

PropTypeDescription
onConnect required(domain: string) => unknownRegisters the domain with the product (normalised and syntax-checked first). Return a promise to show a pending button; reject with an Error to show its message under the field.
childrenReactNodeProduct content after the records, such as a replacement or redirect option.
classNamestring
defaultDomainstring | nullInitial connected domain (uncontrolled). Default `null`.
disabledbooleanDisables every action, for people who may only view the setup.
domainstring | nullThe connected domain (controlled), or `null` while none is connected.
failureReasonReactNodeWhy verification failed, in the product's words. Shown with `status="failed"`.
headingLevel3 | 4 | 6 | 2 | 5Level of the domain and records headings. Default 3.
idstring
inputDescriptionReactNodeHelp under the domain field ("Use the address your visitors will type").
lastCheckedReactNodeWhen the domain was last checked, already formatted ("Checked 2 minutes ago"). Not announced.
messagesCustomDomainMessagesInputOverride visible and announced strings; nested maps may be partial.
onDomainChange((domain: string | null) => void)Called with the domain after `onConnect` succeeds, and with `null` after removal.
onRemove(() => unknown)Disconnects the domain, after a confirmation dialog. Omit to hide Remove.
onVerify(() => unknown)Runs the product's DNS check. While its promise is pending the status shows "Verifying"; the product then sets `status`. Reject to show an error.
providerHintsCustomDomainProviderHint[]Provider instructions written by the product; adds a "Where is your DNS managed?" picker.
recordsCustomDomainRecord[]The records to add, from the product.
requireSubdomainbooleanAsk for a subdomain rather than an apex domain (heuristic: at least three labels).
showRecordsWhenActivebooleanKeep showing the records once the domain is active. Default false.
statusCustomDomainStatusStatus of the connected domain, from the product's checks. Default `pending`.
validate((domain: string) => ReactNode | null | undefined)Extra product check on the normalised domain. Return a message to block connecting.

Variants

PropValuesDescription
status'pending''verifying''active''failed'Set by the product from its DNS checks. While an onVerify promise runs the component shows verifying on its own.
requireSubdomain'false''true'true asks for at least three labels (help.example.com). It cannot know public suffixes such as co.uk; add validate for exact rules.
showRecordsWhenActive'false''true'Keep the records visible after the domain is active, for reference.

States

StateExposed asBehaviour
Enterdata-step="enter"No domain yet: the field and Connect button, step 1 current.
Invalidaria-invalid on the inputSyntax, validate or onConnect error under the field; focus returns to it.
Connectingaria-busy on ConnectWhile the onConnect promise runs.
Pendingdata-step="pending"Records to add, a “Waiting for DNS” badge and the Verify button.
Verifyingdata-step="verifying"Spinner, “Checking your DNS records.”, Verify busy.
Activedata-step="active"Success text and badge; the stepper, records and Verify are hidden.
Faileddata-step="failed"The Verify step shows an error, the product’s failureReason is an alert, and the button reads “Check again”.
Record founddata-found="true|false" on a record“Found” or “Not found yet” per record, from the product’s last check.
Disabled–Every action disabled, for people who may only view.

Interaction behaviour

  • Connect normalises the text, checks syntax, runs validate, then calls onConnect. On success focus moves to the domain heading so screen-reader users hear the next step.
  • Verify calls onVerify; Remove opens a ConfirmDialog and calls onRemove, then focus returns to the empty domain field.
  • Choosing a provider shows that provider’s steps, written by the product.
  • Polling is the product’s job: re-render with a new status, records[].found and lastChecked as checks complete.

Keyboard support

KeysAction
EnterIn the domain field, connects (it is a form).
TabMoves through the field, copy buttons, provider picker and actions.
Enter / SpaceActivates Connect, a copy button, Verify or Remove.
EscapeCloses the remove confirmation without removing.

Accessibility

Role
section (a region landmark only when given an aria-label)
Accessible name
None by default, so several on one page do not repeat a landmark; pass aria-label to make it a named region.
  • The stepper is a nav named “Custom domain setup”, then “Setup of {domain}” once connected, with aria-current="step" and hidden “Step n of 4” text.
  • The domain field is a required Field: its error is linked with aria-describedby and announced politely.
  • The status sentence is a polite live region; “last checked” text sits outside it so a ticking time is not announced. Failure reasons are role="alert".
  • Each copy button is named after its record (“Copy CNAME record 1 value”); record type is read with a hidden “Type:” prefix.
  • Remove is confirmed in an alertdialog whose focus starts on Cancel.
  • Verified with axe in jsdom and in Chromium, Firefox and WebKit on /components/custom-domain-connect.

Responsive behaviour

  • The domain field and Connect button wrap; under 30rem Connect takes the full width.
  • Record cards put Name and Value side by side, and stack them under 30rem. Long values scroll inside their CopyField instead of widening the page.
  • The stepper collapses to “Step n of 4” in narrow containers.

Theme support

  • Monarch tokens only: surfaces from --mn-color-bg-surface and --mn-color-bg-subtle, found records outlined with --mn-color-success-border.
  • Status badges use the warning, info, success and danger tones, so they follow light, dark and high-contrast themes.
  • Spinners stop under prefers-reduced-motion: reduce (Spinner’s own rule).

Do and don't

Do

  • Pass the reason a check failed in plain words, with what to change.
  • Give record purposes (“Proves you own the domain”) so people know why each one is needed.
  • Keep checking in the background and re-render with the new status; tell people they can leave the page.

Don't

  • Do not hard-code provider menus in shared code; pass them as providerHints from the product, where they can be kept current.
  • Do not rely on client-side syntax checks for security; the server must validate again.
  • Do not announce a ticking “checked x seconds ago” time; pass it as lastChecked, which is not live.
  • CopyField: One value with a copy button; used for every record here.
  • Stepper: The step indicator this pattern uses.
  • ConfirmDialog: Confirms removal.
  • SecretField: For tokens that must be hidden until revealed.

Dependencies

KindDepends on
npm packagesreact
Monarch componentsButton, CopyField, Badge, Alert, Spinner, Field, Input, ConfirmDialog, Stepper

Testing coverage

  • normalizeDomain strips schemes, paths, ports and trailing dots, lowercases, and converts international names to punycode.
  • domainSyntaxError rejects empty, single-label, underscore, hyphen-edged, empty-label, numeric-TLD, wildcard, over-long label and over-long names; requireSubdomain.
  • Step 1 validates before onConnect, links the error and focuses the field; typing clears it; axe.
  • Connects the normalised domain (uncontrolled), shows a busy button, focuses the domain heading, reports onDomainChange; axe.
  • Rejected onConnect and product validate messages under the field.
  • Records: type, purpose, TTL, found state, named Name and Value copy fields; axe.
  • Verifying while onVerify runs, then active: stepper, records and Verify hidden.
  • Failed: alert with the reason, error step, “Check again”, rejected verify message, lastChecked outside the live region; axe.
  • Remove: confirm dialog, Cancel keeps the domain, confirm calls onRemove and returns focus to the field.
  • Provider hints picker; disabled; translated messages; renders on the server.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery/custom-domain-connect; Cross-browser axe: /components/custom-domain-connect

Tests: packages/react/src/patterns/technical/custom-domain-connect