Getting started
Installation
Components are copied into your project with the shadcn CLI, so you own every line. Set up the shared pieces once, then add what you need.
Requirements
React 19, Tailwind CSS v4 and TypeScript. Works anywhere the shadcn CLI does — Next.js, Vite, React Router, TanStack Start.
Initialize shadcn
Skip this if your project already has a components.json.
pnpm dlx shadcn@latest initAdd a component
Each component pulls in its npm dependencies, the cn helper and the theme tokens.
pnpm dlx shadcn@latest add https://tuffui.com/r/tool-approval-queue.jsonTheme tokens
Components use your existing background, foreground and muted-foreground, plus a few additive tokens. They install automatically; to add them on their own:
pnpm dlx shadcn@latest add https://tuffui.com/r/theme.jsonManual setup
Not using the CLI? Install the dependencies, add the tokens to your global CSS and create the cn helper.
pnpm add motion lucide-react clsx tailwind-merge:root {
--surface: oklch(0.948 0.002 286);
--surface-inset: oklch(1 0 0);
--surface-raised: oklch(0.912 0.003 286);
--brand: #0071e3;
--brand-foreground: #ffffff;
--positive: #248a3d;
--negative: #d70015;
--warning: #c93400;
--button: linear-gradient(0deg, #fff 0%, #e6e6e6 100%);
--button-hover: linear-gradient(0deg, #fff 0%, #eeeeee 100%);
--button-foreground: #111111;
--button-edge: rgba(0, 0, 0, 0.1);
--button-shadow: 0 0 #0000;
--button-secondary: linear-gradient(180deg, #e2e2e2 0%, #ececec 100%);
--button-secondary-hover: linear-gradient(180deg, #dadada 0%, #e4e4e4 100%);
}
.dark {
--surface: oklch(0.212 0 0);
--surface-inset: oklch(0.172 0 0);
--surface-raised: oklch(0.265 0 0);
--brand: #0a84ff;
--brand-foreground: #ffffff;
--positive: #30d158;
--negative: #ff453a;
--warning: #ff9f0a;
--button: linear-gradient(0deg, #fff 0%, #ababab 100%);
--button-hover: linear-gradient(0deg, #fff 0%, #c2c2c2 100%);
--button-foreground: #111111;
--button-edge: rgba(255, 255, 255, 0.5);
--button-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1);
--button-secondary: linear-gradient(180deg, #1d1d1d 0%, #262626 100%);
--button-secondary-hover: linear-gradient(180deg, #242424 0%, #2d2d2d 100%);
}
@theme inline {
--color-surface: var(--surface);
--color-surface-inset: var(--surface-inset);
--color-surface-raised: var(--surface-raised);
--color-brand: var(--brand);
--color-brand-foreground: var(--brand-foreground);
--color-positive: var(--positive);
--color-negative: var(--negative);
--color-warning: var(--warning);
}import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}Motion
Animations use motion (formerly Framer Motion) with spring physics. Every component wraps itself in MotionConfig reducedMotion="user", so transforms and layout animations turn off when the OS asks for reduced motion — opacity changes stay so state is still visible.
# Demos and previews are driven by a scripted run — handy for your own tests too.
pnpm dlx shadcn@latest add https://tuffui.com/r/use-mock-run.json