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 15 or later with the App Router (Vite, TanStack Start and React Router work too)
  • React 19
  • Tailwind CSS v4
  • Node.js 20.9 or later

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

In an existing Next.js project, run shadcn@latest init --base base instead.

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.

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 -o public/fonts/inter-4.1-variable.woff2 https://www.prfct.dev/fonts/inter-4.1-variable.woff2
curl -o public/fonts/inter-4.1-variable-italic.woff2 https://www.prfct.dev/fonts/inter-4.1-variable-italic.woff2

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

Use them

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

export default function Page() {
  return <Button>Get started</Button>
}
Tested end to end
These steps were tested on a fresh project created with shadcn init --template next --base base: the theme and components install, type-check and build with next build.

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.

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