Skip to main content
Nami Writes - Nami's Blog

Installing shadcn on RedwoodJS project

I tend to reach out shadcn for UI components. But, every time I start with RedwoodJS project, I need to look up how to install it. My goal in this post is to document what I should do.

The instructions are detailed in this GitHub pull request by andyperlitch: #3686 Huge thanks to their work! 🙏

Install Tailwind #

yarn rw setup ui tailwindcss

Add dependencies #

yarn workspace web add tailwindcss-animate class-variance-authority clsx tailwind-merge

Add icon library #

yarn workspace web add lucide-react

Configure path #

In web/tsconfig.json:

"compilerOptions": {
  "paths": {
    "src/*": [
        "./src/*",
        "../.redwood/types/mirror/web/src/*",
        "../api/src/*",
        "../.redwood/types/mirror/api/src/*"
      ],
      "$api/*": ["../api/*"],
      "types/*": ["./types/*", "../types/*"],
      "@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/web"],
+     "@/*": ["./src/*"]
    },
}

(Note that I'm setting the @ path to the /src/, consistent with the convention for used by the Next.js community.)

Configure Tailwind Config #

Following the manual installation doc on shadcn.

+const { fontFamily } = require("tailwindcss/defaultTheme")
+
 /** @type {import('tailwindcss').Config} */
 module.exports = {
   content: ['src/**/*.{js,jsx,ts,tsx}'],
   theme: {
-    extend: {},
+    container: {
+      center: true,
+      padding: '2rem',
+      screens: {
+        '2xl': '1400px',
+      },
+    },
+    extend: {
+      colors: {
+        border: 'hsl(var(--border))',
+        input: 'hsl(var(--input))',
+        ring: 'hsl(var(--ring))',
+        background: 'hsl(var(--background))',
+        foreground: 'hsl(var(--foreground))',
+        primary: {
+          DEFAULT: 'hsl(var(--primary))',
+          foreground: 'hsl(var(--primary-foreground))',
+        },
+        secondary: {
+          DEFAULT: 'hsl(var(--secondary))',
+          foreground: 'hsl(var(--secondary-foreground))',
+        },
+        destructive: {
+          DEFAULT: 'hsl(var(--destructive))',
+          foreground: 'hsl(var(--destructive-foreground))',
+        },
+        muted: {
+          DEFAULT: 'hsl(var(--muted))',
+          foreground: 'hsl(var(--muted-foreground))',
+        },
+        accent: {
+          DEFAULT: 'hsl(var(--accent))',
+          foreground: 'hsl(var(--accent-foreground))',
+        },
+        popover: {
+          DEFAULT: 'hsl(var(--popover))',
+          foreground: 'hsl(var(--popover-foreground))',
+        },
+        card: {
+          DEFAULT: 'hsl(var(--card))',
+          foreground: 'hsl(var(--card-foreground))',
+        },
+      },
+      borderRadius: {
+        lg: `var(--radius)`,
+        md: `calc(var(--radius) - 2px)`,
+        sm: 'calc(var(--radius) - 4px)',
+      },
+      fontFamily: {
+        sans: ['var(--font-sans)', ...fontFamily.sans],
+      },
+      keyframes: {
+        'accordion-down': {
+          from: { height: '0' },
+          to: { height: 'var(--radix-accordion-content-height)' },
+        },
+        'accordion-up': {
+          from: { height: 'var(--radix-accordion-content-height)' },
+          to: { height: '0' },
+        },
+      },
+      animation: {
+        'accordion-down': 'accordion-down 0.2s ease-out',
+        'accordion-up': 'accordion-up 0.2s ease-out',
+      },
+    },
   },
-  plugins: [],
+  plugins: [require('tailwindcss-animate')],
 }

Configure styles #

Update the /web/src/index.css file.

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
	:root {
		--background: 0 0% 100%;
		--foreground: 222.2 47.4% 11.2%;

		--muted: 210 40% 96.1%;
		--muted-foreground: 215.4 16.3% 46.9%;

		--popover: 0 0% 100%;
		--popover-foreground: 222.2 47.4% 11.2%;

		--border: 214.3 31.8% 91.4%;
		--input: 214.3 31.8% 91.4%;

		--card: 0 0% 100%;
		--card-foreground: 222.2 47.4% 11.2%;

		--primary: 222.2 47.4% 11.2%;
		--primary-foreground: 210 40% 98%;

		--secondary: 210 40% 96.1%;
		--secondary-foreground: 222.2 47.4% 11.2%;

		--accent: 210 40% 96.1%;
		--accent-foreground: 222.2 47.4% 11.2%;

		--destructive: 0 100% 50%;
		--destructive-foreground: 210 40% 98%;

		--ring: 215 20.2% 65.1%;

		--radius: 0.5rem;
	}

	.dark {
		--background: 224 71% 4%;
		--foreground: 213 31% 91%;

		--muted: 223 47% 11%;
		--muted-foreground: 215.4 16.3% 56.9%;

		--accent: 216 34% 17%;
		--accent-foreground: 210 40% 98%;

		--popover: 224 71% 4%;
		--popover-foreground: 215 20.2% 65.1%;

		--border: 216 34% 17%;
		--input: 216 34% 17%;

		--card: 224 71% 4%;
		--card-foreground: 213 31% 91%;

		--primary: 210 40% 98%;
		--primary-foreground: 222.2 47.4% 1.2%;

		--secondary: 222.2 47.4% 11.2%;
		--secondary-foreground: 210 40% 98%;

		--destructive: 0 63% 31%;
		--destructive-foreground: 210 40% 98%;

		--ring: 216 34% 17%;

		--radius: 0.5rem;
	}
}

@layer base {
	\* {
		@apply border-border;
	}
	body {
		@apply bg-background text-foreground;
		font-feature-settings: "rlig" 1, "calt" 1;
	}
}

Add a cn helper #

Create /web/src/lib/utils.ts

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

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

Add components.json #

Add /web/components.json file

{
	"$schema": "https://ui.shadcn.com/schema.json",
	"aliases": {
		"components": "@/components",
		"ui": "@/components/ui",
		"utils": "@/lib/utils"
	},
	"style": "default",
	"tailwind": {
		"baseColor": "neutral",
		"config": "./config/tailwind.config.js",
		"css": "@/index.css",
		"cssVariables": true,
		"prefix": ""
	},
	"rsc": false,
	"tsx": true
}

Create an alias for shadcn CLI #

In /package.json:

{
   "private": true,
+  "scripts": {
+    "shad": "cd web && npx shadcn@latest add"
+  },
   "workspaces": {
     "packages": [
       "api",
       "web"
     ]
   },

Try it out #

Now you can create shadcn components, such as a Button:

yarn run shad button