Skip to content
Neon UI
Esc
navigateopen⌘Jpreview
On this page

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.

Create project

Select the region closest to your application.

Neon Auth adds ready-to-use authentication to your app — users and sessions are stored directly in your database. You can also enable it later in project settings.

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

PropType
regions?ServerRegion[]

Regions offered in the picker and plotted on the map.

TypeServerRegion[]
postgresVersions?string[]

Postgres versions offered, newest first.

Typestring[]
Default["18", "17", "16"]
defaultPostgresVersion?string

Initial Postgres version; defaults to the first offered.

Typestring
defaultRegionId?string

Initial region id; defaults to the first region.

Typestring
onSubmit?(values: CreateProjectValues) => void

Called with { name, postgresVersion, regionId, enableAuth } on Create.

Type(values: CreateProjectValues) => void
onCancel?() => void

Renders the Cancel action.

Type() => void
onClose?() => void

Renders the close button in the header.

Type() => void
isBusy?boolean

Locks the actions and swaps the Create label.

Typeboolean
title?ReactNode

Panel heading.

TypeReactNode
Default"Create project"
authDescription?ReactNode

Description under the Neon Auth toggle.

TypeReactNode
latencies?Record<string, number | null>

Measured round-trips by region id, e.g. from useRegionPing.

TypeRecord<string, number | null>
hideGlobe?boolean

Hide the globe panel; the form keeps working alone.

Typeboolean
errors?Partial<Record<"name" | "region", string>>

Inline field errors (e.g. from a rejected create); rendered beside the field with aria-invalid wiring.

TypePartial<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 ServerRegion data if you prefer a 2D map.
  • input and switch — 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.