DEV Community

Zhe
Zhe

Posted on

Designing a Domain Lookup UI Around Evidence, Not a Green Badge

A domain checker looks like a tiny frontend problem: one input, one button, one result. The public interface is simple because the uncertainty has been compressed, not because it has disappeared.

This article does not inspect private source code or architecture. It uses the visible behavior of a public lookup page to build a reusable design checklist for any asynchronous search UI.

Model the result as evidence with provenance

“Available” should not be a boolean floating without context. A useful result model records what answered, what it answered about, and how confident the interface may be.

At minimum, distinguish states such as idle, loading, available, registered, for-sale, needs-confirmation, and error. Preserve a source label for registry RDAP, public registration data, pricing source, or registrar checkout. If one extension fails, do not collapse the entire batch into a generic error.

The public Check Domain page describes registry-backed RDAP checks for supported extensions and separates availability, record, and cost information. That visible separation is a strong UI lesson: different evidence types deserve different components and different copy.

type LookupState =
  | { kind: "idle" }
  | { kind: "loading"; requestedAt: string }
  | { kind: "result"; status: "available" | "registered" | "for-sale" | "confirm"; source: string }
  | { kind: "error"; retryable: boolean; message: string };
Enter fullscreen mode Exit fullscreen mode

This type is an illustrative design proposal, not a claim about the site’s implementation.

Render a batch as progressive, independent rows

A user may submit one label but request many extensions. Those lookups will not necessarily resolve at the same time. The page should keep completed rows stable while pending rows continue to load. Sorting must also be deliberate: dynamically moving results can make users click the wrong item.

For each row, expose:

  • the full candidate domain;
  • current lookup state;
  • evidence source or a plain-language explanation;
  • an action appropriate to that state;
  • a last-checked time when freshness matters.

Avoid using color as the only signal. “Available” and “registered” need visible text, and errors should explain whether retrying is reasonable. Keyboard focus must remain predictable when new rows arrive.

Input handling deserves its own contract. Decide whether the field accepts a bare label, a complete domain, uppercase characters, trailing dots, or internationalized names. Normalize for lookup without silently changing what the user typed, and show the normalized candidate before any external purchase action. An explicit submit action is easier to reason about when one search fans out across many registries.

Keep price data separate from registration state

An available domain is not necessarily the cheapest option. A discounted first year and a higher renewal are two values with different meanings. Put them in separately labeled fields rather than combining them into one ambiguous “from” price.

When building a domain name search, the product contract should specify currency, price freshness, promotional conditions, and what happens when a registrar has no comparable quote. A missing renewal price should render as unknown, not zero.

The final purchase happens outside the lookup interface. A clear boundary message—availability and price must be confirmed at registrar checkout—prevents the product from implying a reservation it cannot provide.

Test uncertainty, not only happy paths

The most valuable tests are rarely “typing example returns a green row.” Cover partial and changing states:

  1. One extension resolves while several remain pending.
  2. A registry times out and a retry later succeeds.
  3. A result changes between lookup and checkout.
  4. Registration price exists but renewal price is missing.
  5. A registered domain returns redacted public data.
  6. A complete domain is entered instead of a bare label.
  7. Screen-reader output announces state changes without flooding the user.

Illustrative Playwright pseudocode could assert stable semantics rather than guessed production selectors:

test("keeps partial results understandable", async ({ page }) => {
  // Selectors and response fixtures must be adapted to the real application.
  await page.getByRole("textbox", { name: /domain/i }).fill("northstar");
  await page.getByRole("button", { name: /search/i }).click();
  await expect(page.getByText(/checking/i)).toBeVisible();
  await expect(page.getByText(/confirm at registrar checkout/i)).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

Make the boundary part of the product

Good uncertainty copy is not legal debris below the fold. It is a functional part of the result. Explain that registry data is time-sensitive, public records may be redacted, pricing can change, and a domain check is not trademark clearance.

The frontend’s job is not to make a complicated system look certain. It is to make each source, state, and next action understandable enough that a user can decide what to verify next.

That principle also improves observability. Track source-level failures without retaining unnecessary search history. Measure timeouts and unsupported extensions separately from negative results, because reliability work depends on knowing whether the registry, pricing source, network, or interface contract failed.

Top comments (0)