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.
"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>
)
}
pnpm dlx shadcn add @01orbital/domain-setupnpx shadcn add @01orbital/domain-setupyarn shadcn add @01orbital/domain-setupbunx --bun shadcn add @01orbital/domain-setupFree under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.
It lands at components/blocks/stepper/domain-setup/index.tsx, as source you own and edit.
import DomainSetup from "@/components/blocks/stepper/domain-setup"
export default function Example() {
return <DomainSetup />
}npm: @phosphor-icons/react. The CLI installs them for you.
Registry pieces it installs with it:
badgebuttoncardfieldfont-geist-monoinputCompact Steps
Small new-project card that walks three steps (name, details, access) with a row of dot indicators and the current step title beside them, for dialogs where a full stepper is too loud.
Domain Setup Bars
Segmented-bar version of Domain Setup: the same domain, DNS record and verification panels in a card, with progress drawn as bars and step titles kept for screen readers.