Skip to content

Installation

Add prfct to a new or existing Next.js project in a few minutes, with the shadcn CLI or by hand.

prfct is distributed as open code: components are copied into your project, where you own them. The shadcn CLI handles the copying and the dependencies; prfct's registry provides the theme and the components.

Requirements

  • Next.js 16 with the App Router
  • React 19
  • Tailwind CSS v4
  • Node.js 22 or later

Install with the CLI

Create a project

Skip this step if you already have a project with shadcn set up. Otherwise, this creates a Next.js app with shadcn configured for Base UI, the primitive library prfct's components are built on:

$ pnpm dlx shadcn@latest init --template next --base base --preset nova

In an existing Next.js project, run shadcn@latest init --base base --preset nova instead. The preset only decides what shadcn init writes before prfct replaces it, so any preset works; naming one skips the question.

Install the prfct theme

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/prfct.json

This copies the tokens, base styles, motion and focus utilities and the font faces into styles/prfct/, adds the token-aware cn() to lib/utils.ts and a ThemeProvider, and installs the dependencies.

The CLI asks before it installs a new style, and again before it replaces lib/utils.ts and components/theme-provider.tsx, which shadcn init created. Answer yes each time, or add --overwrite to the command. prfct's cn() knows the system's own utilities, such as text-heading-md; the default one drops them when classes are merged.

Import the styles

Replace your global stylesheet with the four imports below. prfct's tokens replace the :root, .dark and @theme inline blocks that shadcn init generated — if you keep them, they override prfct's values.

app/globals.css
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@import "../styles/prfct/index.css";

@custom-variant dark (&:where(.dark, .dark *));

Add the fonts

prfct sets its type in Inter 4.1, self-hosted so that every OpenType feature survives subsetting. Download the two files into public/fonts:

curl --create-dirs --output-dir public/fonts \
  -O https://www.prfct.dev/fonts/inter-4.1-variable.woff2 \
  -O https://www.prfct.dev/fonts/inter-4.1-variable-italic.woff2

curl 7.73 or later is needed for --output-dir. Anywhere else, such as Windows PowerShell, save the two files from those addresses into public/fonts yourself.

Then update the root layout: remove the sans font that next/font loads (prfct's stylesheet defines --font-sans), load Geist Mono as --font-geist-mono, preload Inter and wrap the app in the ThemeProvider.

app/layout.tsx
import { Geist_Mono } from "next/font/google"
import ReactDOM from "react-dom"

import { ThemeProvider } from "@/components/theme-provider"
import "./globals.css"

const geistMono = Geist_Mono({ subsets: ["latin"], variable: "--font-geist-mono" })

export default function RootLayout({ children }: { children: React.ReactNode }) {
  ReactDOM.preload("/fonts/inter-4.1-variable.woff2", {
    as: "font",
    type: "font/woff2",
    crossOrigin: "anonymous",
  })

  return (
    <html lang="en" className={geistMono.variable} suppressHydrationWarning>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}

Add components

Add components one at a time, as you need them. Each one brings the components it builds on:

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/button.json

Or register prfct as a namespace once, then use short names:

components.json
{
  "registries": {
    "@prfct": "https://www.prfct.dev/r/{name}.json"
  }
}
$ pnpm dlx shadcn@latest add @prfct/button @prfct/dialog @prfct/field

Add an example app

Every example installs the same way. The command writes the example into components/, adds a page that shows it and installs the components it uses:

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/dashboard.json

Each example's page has a Code view with the files the command writes.

Use them

app/page.tsx
import { Button } from "@/components/ui/button"

export default function Page() {
  return <Button>Get started</Button>
}
Tested end to end
pnpm test:install in prfct's repository runs these steps on a fresh project: it installs the theme, every component and every example from this documentation with the latest release of each dependency, then type-checks, lints and builds the result.

Manual installation

Prefer to see every file? Each component page has a Manual tab with its source and dependencies. The foundation is small:

  1. Styles. Copy the five files into styles/prfct/, naming styles/prfct.css index.css. It imports fonts.css, tokens.css, base.css and utilities.css from the same folder. Import styles/prfct/index.css from your global stylesheet after Tailwind, as above.
  2. Utilities. lib/utils.ts exports the token-aware cn(). Install its one dependency with pnpm add cn.
  3. Fonts. The two Inter files in public/fonts, and Geist Mono through next/font as --font-geist-mono. Without it, code falls back to the platform's monospace.
  4. Packages. @base-ui/react, class-variance-authority, lucide-react, next-themes, shadcn (for its Tailwind variants) and tw-animate-css.
  5. Theme provider. Copy components/theme-provider.tsx, the thin wrapper around next-themes that the layout above renders.

Dark mode

The ThemeProvider follows the system preference and stores the person's choice. Components never use dark: — they read tokens that switch with the .dark class, which also works on any subtree. See Theming.

Updating

Because the code is yours, updates are opt-in. Preview what changed upstream before accepting it:

$ pnpm dlx shadcn@latest add @prfct/button --diff button.tsx