# Domain Setup

> Custom-domain card in three numbered steps: enter the domain, copy the A and CNAME records from a monospace block, then verify with a pending or active badge. The check is a stand-in.

A shadcn/ui block from the [@01orbital](https://01orbital.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/01orbital/domain-setup).

- Page, with a live preview: https://shadcn.exchange/r/01orbital/domain-setup
- Screenshot: https://shadcn.exchange/shot/01orbital/domain-setup/light.png
- Registry item: https://01orbital.dev/r/domain-setup.json
- Categories: stepper
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/01orbital/domain-setup.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@phosphor-icons/react`
- Registry items: `@01orbital/badge`, `@01orbital/button`, `card`, `@01orbital/field`, `@01orbital/font-geist-mono`, `@01orbital/input`, `@01orbital/stepper`

## Files

### `blocks/stepper/domain-setup/index.tsx`

```tsx
"use client"

import { useId, useState } from "react"
import { CheckIcon } from "@phosphor-icons/react"

import {
  Stepper,
  StepperContent,
  StepperIndicator,
  StepperItem,
  StepperList,
  StepperSeparator,
  StepperTitle,
  StepperTrigger,
} from "@/components/stepper/stepper"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Field, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"

// The copy: change text here, not in the markup.
const COPY = {
  title: "Add a custom domain",
  body: "Point your domain at Orbcore and we issue the certificate.",
  back: "Back",
  next: "Continue",
  verify: "Verify",
  steps: ["Domain", "DNS records", "Verify"],
  domain: "Domain",
  dnsHint:
    "Add both records at your DNS provider. Changes can take up to an hour.",
  verifying: "We check the records every minute until they resolve.",
  pending: "Waiting for DNS",
  verified: "Domain active",
  verifiedBody: "Both records resolve. The certificate is issued.",
} as const

const RECORDS = [
  { type: "A", name: "@", value: "76.76.21.21" },
  { type: "CNAME", name: "www", value: "cname.orbcore.dev" },
] as const

export default function DomainSetup() {
  const id = useId()
  const [step, setStep] = useState(1)
  // Local stand-in for the DNS check: a real block polls your API here.
  const [verified, setVerified] = useState(false)
  const last = COPY.steps.length - 1
  return (
    <Card className="mx-auto w-full max-w-lg">
      <CardHeader>
        <CardTitle>{COPY.title}</CardTitle>
        <CardDescription>{COPY.body}</CardDescription>
      </CardHeader>
      <CardContent>
        <Stepper value={step} onValueChange={setStep}>
          <StepperList>
            {COPY.steps.map((title, i) => (
              <StepperItem key={title} value={i}>
                <StepperTrigger disabled={verified}>
                  <StepperIndicator className="size-7 text-xs">
                    <CheckIcon
                      weight="bold"
                      aria-hidden
                      className="hidden size-3.5 group-data-[state=completed]/step:block"
                    />
                    <span className="group-data-[state=completed]/step:hidden">
                      {i + 1}
                    </span>
                  </StepperIndicator>
                  <StepperTitle className="sr-only sm:not-sr-only">
                    {title}
                  </StepperTitle>
                </StepperTrigger>
                <StepperSeparator className="group-last/step:hidden" />
              </StepperItem>
            ))}
          </StepperList>
          <StepperContent keepMounted value={0}>
            <Field>
              <FieldLabel htmlFor={`${id}dom`}>{COPY.domain}</FieldLabel>
              <Input id={`${id}dom`} defaultValue="orbcore.dev" />
            </Field>
          </StepperContent>
          <StepperContent keepMounted value={1} className="gap-3">
            <p className="text-sm text-muted-foreground">{COPY.dnsHint}</p>
            <pre className="overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs leading-6">
              {RECORDS.map(
                (r) => `${r.type.padEnd(6)}${r.name.padEnd(6)}${r.value}`
              ).join("\n")}
            </pre>
          </StepperContent>
          <StepperContent keepMounted value={2} className="gap-3">
            <p className="text-sm text-muted-foreground" aria-live="polite">
              {verified ? COPY.verifiedBody : COPY.verifying}
            </p>
            <Badge variant={verified ? "default" : "outline"} className="w-fit">
              {verified ? COPY.verified : COPY.pending}
            </Badge>
          </StepperContent>
        </Stepper>
      </CardContent>
      <CardFooter className="justify-between">
        <Button
          variant="outline"
          disabled={step === 0 || verified}
          onClick={() => setStep(step - 1)}
        >
          {COPY.back}
        </Button>
        <Button
          disabled={verified}
          onClick={() => (step < last ? setStep(step + 1) : setVerified(true))}
        >
          {step < last ? COPY.next : COPY.verify}
        </Button>
      </CardFooter>
    </Card>
  )
}
```
