CreateProject
Create-project onboarding panel — project form on the left, the interactive region map on the right.
CreateProject is the first neon-ui block: a full onboarding panel assembled from registry components. The left column holds the project form — name input, Postgres version and region selects, and a Neon Auth switch — while a tilted RegionGlobe fills the right, synced two-way with the region field: pick from the select or click a dot on the globe, and it eases around to face the region. A floating card overlays the globe with the server info — provider, name, region id, and (when you pass latencies, e.g. from useRegionPing) your measured round-trip. Two small affordances round it out: a dice button in the name field generates a Neon-style project name, and when latencies are known a fastest · x ms action next to the Region label jumps straight to the lowest-ping region. It’s a self-contained <form>: drop it on a page or inside a Dialog, wire onSubmit, and it works. Sizing is container-query driven — the block responds to its own width, not the viewport — so the globe appears only when the panel is wide enough (≥48rem), fields and actions stack on narrow panels, and it behaves in dialogs, sidebars, and split views without media-query surprises.
Install
npx shadcn@latest add https://ui.neon.com/r/create-project.json
Installing the block pulls its component tree: region-globe (and its region-select types), input, switch, select, and button.
Usage
import { CreateProject } from "@/components/neon-ui/create-project";
<CreateProject
regions={regions}
defaultRegionId="aws-us-east-1"
isBusy={isCreating}
onSubmit={(values) => createProject(values)}
onCancel={() => router.back()}
/>;
Wiring to Neon
A server component loads the region list, and a server action creates the project from the submitted values:
import { createNeonClient } from "@/lib/neon-client";
const neon = createNeonClient(process.env.NEON_API_KEY);
const { data } = await neon.regions.list();
const regions = (data ?? []).map((region) => ({
id: region.region_id,
name: region.name,
provider: region.region_id.split("-")[0].toUpperCase(),
lat: Number(region.geo_lat),
lng: Number(region.geo_long),
}));
const createProject = async (values: CreateProjectValues) => {
"use server";
const { data: project } = await neon.projects.create({
name: values.name,
region_id: values.regionId,
pg_version: Number(values.postgresVersion) as PgVersion,
});
return project?.id;
};
geo_lat and geo_long arrive as strings and are empty when unknown, so drop regions that fail to parse before plotting them. Pass the action to onSubmit from a client wrapper, hold isBusy while it runs, and map a rejected create onto errors so the message lands beside the field. projects.createAndConnect is the one-call alternative: it waits for provisioning and hands back a ready connection string with the project.
Props
regions?ServerRegion[]
Regions offered in the picker and plotted on the map.
ServerRegion[]postgresVersions?string[]
Postgres versions offered, newest first.
string[]["18", "17", "16"]defaultPostgresVersion?string
Initial Postgres version; defaults to the first offered.
stringdefaultRegionId?string
Initial region id; defaults to the first region.
stringonSubmit?(values: CreateProjectValues) => void
Called with { name, postgresVersion, regionId, enableAuth } on Create.
(values: CreateProjectValues) => voidonCancel?() => void
Renders the Cancel action.
() => voidonClose?() => void
Renders the close button in the header.
() => voidisBusy?boolean
Locks the actions and swaps the Create label.
booleantitle?ReactNode
Panel heading.
ReactNode"Create project"authDescription?ReactNode
Description under the Neon Auth toggle.
ReactNodelatencies?Record<string, number | null>
Measured round-trips by region id, e.g. from useRegionPing.
Record<string, number | null>hideGlobe?boolean
Hide the globe panel; the form keeps working alone.
booleanerrors?Partial<Record<"name" | "region", string>>
Inline field errors (e.g. from a rejected create); rendered beside the field with aria-invalid wiring.
Partial<Record<"name" | "region", string>>Extracted components
The block is deliberately thin — its pieces shipped as registry items first:
- RegionGlobe — the tilted spinning globe with clickable dots.
use-region-ping— the live-latency hook, installable on its own (https://ui.neon.com/r/use-region-ping.json); no component depends on it.- RegionSelect — the flat dot-matrix map picker; no longer inside the block, but it pairs with the same
ServerRegiondata if you prefer a 2D map. inputandswitch— Base UI primitives extracted for the form, installable on their own (https://ui.neon.com/r/input.json,https://ui.neon.com/r/switch.json).
Accessibility
- The panel is a labelled
<form>; every field has a real<label>. - The region field’s select is the accessible input; the map markers are an enhancement on top of the same state.
- Create is a submit button, so Enter anywhere in the form creates the project.