Shadcn with NextJS, Tailwind, Yarn on MacOS (2024)

John Maeda
3 min readSep 1, 2024

--

Want to get Shadcn running with NextJS, Tailwind, and yarn?? You’ve come to the right place in 2024.

Got Node?

Be sure to have the right node version installed and use nvm to reduce your confusion. This was the correct one in 2024 of August for me.

nvm install 20.9.0
nvm use 20.9.0

Make a NextJS app

You can either keep it simple (JS) or make it more complex (TS + linter).

yarn create next-app . --tailwind
yarn create next-app . --tailwind --typescript --eslint

Throw in Shadcn

You get a wizard once you cast the shadcn spell.

npx shadcn@latest init

Go ahead and yarn

I’m so used to doing npm but I know yarn makes me cooler.

yarn dev
If this happened, congratulations! You can now see a Vercel advert :-)

Add a fancy shadcn component

I’m partial to terms of services checkboxes so …

npx shadcn@latest add checkbox

Then go to src/app/page.js and add in the sample code you found on the checkbox page over at shadcn/ui.

You get a new Vercel advert like below.

Leverage the magic of Tailwind

You can mess with the classes and create a checkbox like this:

With the restyling of the Checkbox’s CSS that uses Tailwind.

...
<div className="flex items-center space-x-3">
<Checkbox
id="terms"
className="h-5 w-5 text-blue-500 border-gray-300 rounded focus:ring-blue-500 focus:ring-2 dark:text-blue-500 dark:border-gray-600 dark:focus:ring-blue-500 hover:border-blue-700 hover:bg-blue-100"
/>
<label
htmlFor="terms"
className="text-sm font-medium text-gray-900 dark:text-gray-300 cursor-pointer"
>
Accept terms and conditions
</label>
</div>
...

What else can you do?

I saw a demo of the Sonner component on YouTube the other day and definitely wanted it for myself. I can’t resist a beautiful toast. Go and get a button component, and also the sonner component.

npx shadcn@latest add button
npx shadcn@latest add sonner

You’ll need to change the components/ui/button.jsx at the very top.

And modify your src/app/layout.js to add the Toaster reference.

import { Inter } from "next/font/google";
import "./globals.css";

import { Toaster } from 'sonner';

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<Toaster />
{children}</body>
</html>
);
}

Lastly, let’s stop pretending we’re a Vercel advert and change page.js.

'use client';

import { toast } from 'sonner';
import { Button } from '@/components/ui/button';

export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<Button
variant="outline"
onClick={() => {
console.log("Button clicked");
toast("Event has been created", {
description: "Sunday, December 03, 2023 at 9:00 AM",
action: {
label: "Undo",
onClick: () => console.log("Undo clicked"),
},
});
}}
>
Show Toast
</Button>
</main>
);
}

And then you get something pretty wonderful.

Some delicious, hot hot hot toast!

What to do next?

Now that I’ve figured this out, I definitely need to do some more stuff. I hope this works for you! My repo is over here. —JM

--

--

John Maeda

John Maeda: Technologist and product experience leader that bridges business, engineering, design via working inclusively. Currently VP Design and A.I. at MSFT.