← Back to list

How to Set Up a Turborepo with Next.js, TypeScript, Tailwind CSS v4, and shadcn/ui

A practical guide to building a fast, type-safe monorepo with shared UI components

Amir · 2026-01-07 21:35 · 9 claps · 4.8 min read
#nextjs #shadcn #turborepo #tailwind-css #web-development
Open on Medium ↗
Wiki topics: 🌐 · Web Development

How to Set Up a Turborepo with Next.js, TypeScript, Tailwind CSS v4, and shadcn/ui

A practical guide to building a fast, type-safe monorepo with shared UI components

Let’s skip the fluff and get straight into the setup.

Step 1: Create a New Turborepo

Start by generating a fresh Turborepo project. This template already includes two Next.js applications.

bunx create-turbo@latest

You’ll be prompted to choose:

  • A project name
  • Your preferred package manager (this guide uses Bun)

Once the command finishes successfully, open the project in your IDE.

You should see two main directories:

apps/
packages/

Navigate to:

packages/ui/src

Delete any existing components inside this folder. We’ll be using shadcn/ui instead of the default components.

Step 2: Install Tailwind CSS in the UI Package

Move into the UI package:

cd packages/ui

Install Tailwind CSS and its PostCSS dependencies:

in /apps/web run : bun add -D @tailwindcss/postcss postcss

And Then in /packages/ui run :

bun add tailwindcss @tailwindcss/postcss postcss

Step 3: Configure PostCSS

Create a PostCSS config file:

**/packages/ui/postcss.config.mjs**

export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
}

Step 4: Add Global Styles

Create the following file:

**/packages/ui/src/styles/globals.css**

@import "tailwindcss";

This file will act as the shared global stylesheet for your UI package.

Step 5: Manually Configure shadcn/ui

We’ll now prepare the UI package so it works smoothly with shadcn in a Turborepo setup.

Install Required Dependencies

From inside packages/ui, run:

bun add class-variance-authority clsx tailwind-merge lucide-react tw-animate-css

Configure TypeScript Path Aliases

Update the UI package’s tsconfig.json:

{
  "extends": "@repo/typescript-config/react-library.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@repo/ui/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Define Global Theme Styles

Open **/packages/ui/src/styles/globals.css** and replace its contents with:

@import "tailwindcss";
@import "tailwindcss";
@import "tw-animate-css";

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

:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.145 0 0);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);
  --secondary-foreground: oklch(0.205 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --accent: oklch(0.97 0 0);
  --accent-foreground: oklch(0.205 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(0.577 0.245 27.325);
  --border: oklch(0.922 0 0);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);
  --chart-1: oklch(0.646 0.222 41.116);
  --chart-2: oklch(0.6 0.118 184.704);
  --chart-3: oklch(0.398 0.07 227.392);
  --chart-4: oklch(0.828 0.189 84.429);
  --chart-5: oklch(0.769 0.188 70.08);
  --radius: 0.625rem;
  --sidebar: oklch(0.985 0 0);
  --sidebar-foreground: oklch(0.145 0 0);
  --sidebar-primary: oklch(0.205 0 0);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.97 0 0);
  --sidebar-accent-foreground: oklch(0.205 0 0);
  --sidebar-border: oklch(0.922 0 0);
  --sidebar-ring: oklch(0.708 0 0);
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.145 0 0);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.145 0 0);
  --popover-foreground: oklch(0.985 0 0);
  --primary: oklch(0.985 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.396 0.141 25.723);
  --destructive-foreground: oklch(0.637 0.237 25.331);
  --border: oklch(0.269 0 0);
  --input: oklch(0.269 0 0);
  --ring: oklch(0.439 0 0);
  --chart-1: oklch(0.488 0.243 264.376);
  --chart-2: oklch(0.696 0.17 162.48);
  --chart-3: oklch(0.769 0.188 70.08);
  --chart-4: oklch(0.627 0.265 303.9);
  --chart-5: oklch(0.645 0.246 16.439);
  --sidebar: oklch(0.205 0 0);
  --sidebar-foreground: oklch(0.985 0 0);
  --sidebar-primary: oklch(0.488 0.243 264.376);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.269 0 0);
  --sidebar-accent-foreground: oklch(0.985 0 0);
  --sidebar-border: oklch(0.269 0 0);
  --sidebar-ring: oklch(0.439 0 0);
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Add the cn Utility Function

Create this helper file:

**/packages/ui/src/lib/utils.ts**

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

This utility is used by most shadcn components to merge Tailwind class names correctly.

Add components.json for shadcn

Create the following file in the UI package root:

**/packages/ui/components.json**

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/styles/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@repo/ui/components",
    "ui": "@repo/ui/components",
    "utils": "@repo/ui/lib/utils",
    "lib": "@repo/ui/lib",
    "hooks": "@repo/ui/hooks"
  },
  "iconLibrary": "lucide"
}

Important Tailwind Source Configuration

To ensure Tailwind picks up classes used in the UI package, update **globals.css** again:

@import "tailwindcss";
@import "tw-animate-css";

@source "../../../../packages/ui/src/**/*.{js,ts,jsx,tsx}"; /* Add This line */

This prevents Tailwind from purging styles used in shared components.

Adding shadcn Components

From inside the UI package, you can now add components:

bunx --bun shadcn@latest add button

This will generate:

src/components/button.tsx

Export UI Package Files

Update **/packages/ui/package.json**:

{
  "exports": {
    "./globals.css": "./src/styles/globals.css",
    "./postcss.config": "./postcss.config.mjs",
    "./lib/*": "./src/lib/*.ts",
    "./hooks/*": [
      "./src/hooks/*.ts",
      "./src/hooks/*.tsx"
    ],
    "./components/*": "./src/components/*.tsx"
  }
}

Using the UI Package in a Next.js App

Add the Dependency

Inside your Next.js app (/apps/web), ensure @repo/ui is listed:

{
  "dependencies": {
    "@repo/ui": "*"
  }
}

Then install dependencies:

bun install

Configure PostCSS in Next.js

Create:

**/apps/web/postcss.config.mjs**

export { default } from "@repo/ui/postcss.config";

Update TypeScript Paths

Modify **/apps/web/tsconfig.json**:

{
  "paths": {
    "@/*": ["./src/*"],
    "@repo/ui/*": ["../../packages/ui/src/*"]
  }
}

Update Next.js Configuration

Edit **/apps/web/next.config.ts**:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  transpilePackages: ["@repo/ui"],
};

export default nextConfig;

This ensures Next.js properly transpiles the shared UI package.

Add shadcn Config for the App

Create:

**/apps/web/components.json**

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "../../packages/ui/src/styles/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "iconLibrary": "lucide",
  "aliases": {
    "components": "@/components",
    "hooks": "@/hooks",
    "lib": "@/lib",
    "utils": "@repo/ui/lib/utils",
    "ui": "@repo/ui/components"
  }
}

Import Shared Global Styles

Delete the default globals.css in your Next.js app and import the shared one in your root layout:

**/apps/web/app/layout.tsx**

import "@repo/ui/globals.css";

🎉 You’re All Set

You can now use shadcn components directly from your shared UI package:

import { Button, buttonVariants } from "@repo/ui/components/button";

This setup gives you:

  • A clean Turborepo structure
  • Shared Tailwind styles
  • Centralized shadcn components
  • Full type safety across apps

Sample repo : https://github.com/amirjld/turborepo-sample


메타데이터
post_id
1d0535ea160f
slug
how-to-set-up-a-turborepo-with-next-js-typescript-tailwind-css-v4-and-shadcn-ui-1d0535ea160f
url
https://medium.com/@amirjld/how-to-set-up-a-turborepo-with-next-js-typescript-tailwind-css-v4-and-shadcn-ui-1d0535ea160f
canonical_url
https://medium.com/@amirjld/how-to-set-up-a-turborepo-with-next-js-typescript-tailwind-css-v4-and-shadcn-ui-1d0535ea160f
author_url
https://medium.com/@amirjld
status
ok
fetched_at
2026-06-12 18:14:10