Updated: September 30, 2026

P1 Component Library


The P1 component library is a shadcn registry hosted at components.p1.pantheon.io. It ships 37 pre-built Puck blocks styled to the P1 design system. Because it is a registry rather than an npm package, shadcn copies source files directly into your project — you own them and can edit them freely.

Browse the full catalog and copy install commands at components.p1.pantheon.io.

New project: install during scaffolding

The create-p1-starter-kit scaffolder includes the library as an opt-in step.

pnpm create @pantheon-systems/p1-starter-kit

The prompts walk through project name, package manager, and git setup, then ask:

Include the P1 starter component library? (default: Yes)

If you answer Yes, the scaffolder installs all the blocks from the library into components/puck/blocks/<name>/, adds the @p1/tokens import to app/styles.css, and writes components.json. It prints Installed n P1 blocks and shows a worked registration example for one block.

If you answer No, the project still gets components.json — so you can add blocks later with shadcn add @p1/<name> without looking up any URLs. Declining costs nothing and is fully reversible.

Either way, the scaffolder wires the @/* TypeScript alias, the blocks barrel, and the puck.config.tsx spreads. Nothing to configure by hand.

Flags: --blocks or --no-blocks skips the prompt. --yes accepts all defaults, including Yes to the library.

---

Existing project: enable the registry

A project created before the registry existed will fail with Unknown registry "@p1" when you run shadcn add @p1/…. One command fixes this:

npx @pantheon-systems/p1-next-sdk enable-registry

Pass a directory path as the first argument if you are running it from outside the project root.

The command writes three things and skips anything already present, so a second run changes nothing:

  1. components.json — created if absent; if one exists, only the registries entry is added.
  2. components/puck/blocks/index.ts — the blocks barrel.
  3. tsconfig.json — the @/* path alias, edited as text so your comments and formatting survive. If it cannot edit the file safely, it prints the lines to add instead.

It does not touch puck.config.tsx. That file is yours and likely edited. The two spreads to add are printed on the terminal after the command runs — see Registering a block below for the ordering rule.

The command also checks your vitest config. If the @/ alias is missing there, it flags that as a required action. Vitest does not read tsconfig paths, so a test that imports an installed block will fail to resolve rather than fail to assert.

Adding blocks

Install the full library

# pnpm
pnpm dlx shadcn@latest add @p1/base

# npm
npx shadcn@latest add @p1/base

# Yarn Berry
yarn dlx shadcn@latest add @p1/base

@p1/base installs 29 blocks plus @p1/tokens. Eight blocks are intentionally excluded from @p1/base because they would shadow blocks the starter kit ships under the same name: button, divider, heading, image, list, paragraph, quote, and spacer. You can install any of them individually if you want them.

Install one block

# pnpm
pnpm dlx shadcn@latest add @p1/pricing

# npm
npx shadcn@latest add @p1/pricing

Review changes to a block you have already edited

If you have customized a block, use --diff to compare just the file you care about rather than seeing the full item:

pnpm dlx shadcn@latest add @p1/pricing --diff components/puck/blocks/pricing/pricing.tsx

Merge the diff manually. There is no automatic update push.

Registering a block

Installing a block puts files on disk — it does not add the block to the Puck editor. Registration is a three-line paste into components/puck/blocks/index.ts.

The registry serves the exact lines alongside every install command, and the catalog card at components.p1.pantheon.io has a Copy button for the same text. Example for @p1/pricing:

import { PricingBlock } from "./pricing/pricing.block";

// in p1Blocks
P1Pricing: PricingBlock,

// in p1Categories — create the entry if it does not exist yet:
p1Convert: { title: "P1 Convert", components: ["P1Pricing"] },
// or, if p1Convert already exists, add to its components array:
// p1Convert: { title: "P1 Convert", components: ["P1Pricing", "P1CTA"] },

Three things that cause silent failures

The export name is not always the directory name. Three blocks differ: cta exports CtaBannerBlock, features exports FeatureCardsBlock, logos exports LogoCloudBlock. Using the wrong name gives you undefined, and an undefined component is absent from the editor drawer with no error. Use the lines the registry serves, or check the top of components/puck/blocks/<name>/<name>.block.tsx directly.

The category line is what puts a block in the drawer. A block added only to p1Blocks is registered but invisible in the editor. Both the p1Blocks entry and the p1Categories entry are required.

The spreads in puck.config.tsx must come first. Later keys win in a JavaScript object literal, so placing a spread after your own keys lets a registry category silently overwrite one of yours — the blocks stay registered and disappear from the drawer with no error. Correct order:

import { p1Blocks, p1Categories } from "./components/puck/blocks";

components: { ...p1Blocks,     /* your blocks after */ },
categories: { ...p1Categories, /* your categories after */ },

After registering all blocks, the merged Puck config contains 41 components across 14 categories — the starter kit's 12 components and 6 categories, plus 29 blocks across 8 P1 categories. Before registration, the editor shows only the starter kit's 12 components across 6 categories. The installed blocks sit on disk and cost nothing; they simply do not appear in the editor until registered.

Auto-registration is planned and tracked separately. The paste-in step is the current mechanism.

Keeping blocks up to date

The registry does not push updates. To review what changed in a block since you installed it:

pnpm dlx shadcn@latest add @p1/<name> --diff components/puck/blocks/<name>/<name>.tsx

Review the diff and merge changes manually. A project that never runs --diff stays on the version it was scaffolded with.

Troubleshooting

Unknown registry "@p1" — components.json is missing or has no registries entry. Run npx @pantheon-systems/p1-next-sdk enable-registry.

Block installed but missing from the editor drawer — the block is not registered, is registered in no category, or there is a category-key collision. Count blocks in the drawer, not directories on disk.

Test fails to resolve an import from an installed block — the @/ alias is missing from your vitest config. tsconfig paths are not read by vitest. Add the alias; enable-registry prints the lines if it detected this gap during setup.

Tokens @import not applying — the @import must appear before every other CSS rule in app/styles.css. The CLI places it correctly; moving it below other rules by hand causes the browser to drop it silently.

Block category disappeared from the drawer — a spread placed after your own keys in puck.config.tsx caused a category-key collision. Move ...p1Categories before your own categories.