Building a static Next.js Page with Shadcn UI Components: A Complete Guide

Building a static Next.js Page with Shadcn UI Components: A Complete Guide

Getting started with shadcn/ui

ShadCN UI is a collection of reusable components that can be used to build web applications. These components are built on Radix UI, a headless UI library that provides accessibility features right out of the box. This means that Radix UI provides functional building blocks that handle behavior and interaction, while allowing you to apply your own styling. ShadCN UI applies the styling out of the box, but also makes it customizable and highly reusable with the help of class variants.

To install next-js in your current directory run the following command:

npx create-next-app@latest .

or

npx create-next-app@latest my-app --typescript --tailwind --eslint

By leveraging Radix UI, ShadCN UI inherits built-in accessibility support, allowing you to create inclusive and user-friendly interfaces without additional effort. To get started with ShadCN UI, you can simply execute the command

npx shadcn-ui@latest init

to install the basic utils and modify your Tailwind config files and global.css.

The installation process for ShadCN UI is thoroughly explained in the documentation. 😊

Here is the link to the documentation :

https://ui.shadcn.com/docs/installation/next

Adding Components to Your Application

To add components to your app using ShadCN UI, you can start with the Button component. According to the ShadCN UI documentation, adding components is as simple as copying and pasting the code into your project and customizing it to your needs. The process is made even more convenient with the help of the CLI.

Instead of manually creating a file, you can use the command to add the Button component to your project.

npx shadcn-ui@latest add button

After running this comand this will create ui folder inside components folder to store all components like the button component.

After adding the component, you need to import it into your application by using import { Button } from 'ui/button';.

There are two steps involved in using a component from ShadCN UI: adding the component and then using it in your app. This will provide you with a basic, yet beautifully designed, component. However, there is a second step to consider, which is customizing the component. To understand how customization works, you can explore the ui/button.tsx file.

Now we will add all these components in ui folder using our CLI:

  1. Button: npx shadcn-ui@latest add button

  2. Avatar: npx shadcn-ui@latest add avatar

  3. Calendar : npx shadcn-ui@latest add calendar

  4. Command : npx shadcn-ui@latest add command

  5. Dialog : npx shadcn-ui@latest add dialog

  6. Menubar : npx shadcn-ui@latest add menubar

  7. Navigation-menu : npx shadcn-ui@latest add navigation-menu

  8. popover : npx shadcn-ui@latest add popover

  9. card : npx shadcn-ui@latest add card

Here is the structure of my files given in the documentation

https://ui.shadcn.com/docs/installation/next#app-structure

You can use this as a reference !

Building a simple static page using shadcn/ui

In this blog we will be trying to improve the Indigo Airline page using shadcn/ui and next js.

First we will create our theme-povider component in or components folder:

Filename: components/theme-provider.tsx

Filename : app/layout.tsx


import './globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { ThemeProvider } from '@/components/theme-provider'
import Navbar from '@/components/Navbar'
import { Orbitron, Roboto } from "next/font/google"
const orbit = Orbitron({
  subsets: ['latin'],
  weight:'900',
  display: 'swap',
})
const inter = Inter({ subsets: ['latin'] })

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

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={orbit.className}>
      <ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
            {children}
      </ThemeProvider>
      </body>
    </html>
  )
}

The above code is a React component that defines the root layout of a Next.js application. It imports several modules, including ThemeProvider from @/components/theme-provider, Orbitron and Roboto from next/font/google, and Metadata from next. The RootLayout component returns an HTML structure with a ThemeProvider component wrapping the children passed to it. The ThemeProvider component has been configured with the attribute prop set to "class", the defaultTheme prop set to "dark", and the enableSystem prop. This means that the default theme for the application will be dark, but the user’s system settings will be used to determine the actual theme if possible.

In the code we are also configuring an instance of the Orbitron font with the subsets prop set to ['latin'], the weight prop set to '900', and the display prop set to 'swap'. The className of this instance is then used to set the className of the body element in the HTML structure returned by the RootLayout component.

Next we do some changes into

Filename: app/page.tsx

import Image from 'next/image'
import Navbar from '@/components/Navbar'
import Content from '@/components/Content'
export default function Home() {
  return (
    <main className="p-10">
      <Navbar />
      <Content />
    </main>
  )
}

The Home component returns a main element with the className prop set to "p-10". Inside the main element, there are two components: Navbar and Content. These components are imported from the @/components/Navbar and @/components/Content modules, respectively.

Building 🛠️

Now are you ready to build an awesome website with Next.js and shadcn/ui? 🚀 Let’s get started by creating our Navbar and Content components! These components will be the building blocks of our home page, displaying the navigation bar and the main content of the page, respectively. With just a few lines of code, we can import these components and use them in our Home component to create a beautiful and functional home page. Let’s do this! 😎

We will start by creating Navbar for our Home page.

Filename: components/Navbar.tsx

"use client"
import React from 'react'
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuIndicator,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from "@/components/ui/navigation-menu"

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"

const Navbar = () => {
  return (
    <div className="flex flex-column p-10 justify-between bg-transparent ">
      {/* Title */}
      <span className="text-[40px]">Indigo ✈️</span>
      <div className="flex flex-row gap-4 justify-center items-center">
        {/* Navigation Menu */}
        <NavigationMenu>
          <NavigationMenuList>
            <NavigationMenuItem>
              <NavigationMenuTrigger>
                <span className="text-[20px]">Book</span>
              </NavigationMenuTrigger>
            </NavigationMenuItem>
            <NavigationMenuItem>
              <NavigationMenuTrigger>
                <span className="text-[20px]">Check-In</span>
              </NavigationMenuTrigger>
            </NavigationMenuItem>
            <NavigationMenuItem>
              <NavigationMenuTrigger>
                <span className="text-[20px]">Manage</span>
              </NavigationMenuTrigger>
            </NavigationMenuItem>
            <NavigationMenuItem>
              <NavigationMenuTrigger>
                <span className="text-[20px]">Info</span>
              </NavigationMenuTrigger>
            </NavigationMenuItem>
            <NavigationMenuItem>
              <NavigationMenuTrigger>
                <span className="text-[20px]">Login</span>
              </NavigationMenuTrigger>
            </NavigationMenuItem>
          </NavigationMenuList>
        </NavigationMenu>

        {/* Avatar */}
        <Avatar>
          <AvatarImage src="https://github.com/shadcn.png" />
          <AvatarFallback>CN</AvatarFallback>
        </Avatar>

      </div>

    </div>

  )
}

export default Navbar

In the above code we are defining a navigation bar for our website called “Indigo ✈️”. The navigation bar has a title and several menu items, such as “Book”, “Check-In”, “Manage”, “Info”, and “Login”. There’s also an avatar component that displays the user’s profile picture or their initials if the picture is not available. The code uses several custom components, such as NavigationMenu, NavigationMenuItem, and Avatar, to create the navigation bar. It also imports two fonts from Google Fonts: Orbitron and Roboto. Overall, this code creates a sleek and modern navigation bar for the Indigo website! 😊

After coding the page should look something like this :

Now let’s imagine we are on a mission to create a Content.tsx page for our Next.js app, using some cool components from the next/link, @/components/ui/navigation-menu, and @/components/ui/button modules, as well as some custom components like Search and DatePickerWithRange. Our page is going to have a navigation menu with a link to the home page, a title that says “Flight Booking at Low-cost”, and a one-way/round-trip toggle. We’ll also have two search fields for the origin and destination, a date picker for the travel dates, and a big “Search Flight” button to find the best deals and some card deals to get you the best offer. It’s going to be awesome! 🛫

Now before creating Content.tsx we first need to build some other components like Search.tsx , Flightcard.tsx , Datepicker.tsx. So let's build our first component for Content section for our page.

Note: All these components for our content section can also be imported from the documentation.

Filename: components/Flightcard.tsx

import { CircleIcon } from "@radix-ui/react-icons";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "./ui/card";

export function Flightcard() {
  return (
    <Card>
      <CardHeader className="grid items-start gap-4 space-y-0">
        <div className="space-y-1">
          <CardTitle>Indigo offers</CardTitle>
          <CardDescription>
            Get upto 10% off on indigo app bookings
          </CardDescription>
        </div>
      </CardHeader>
      <CardContent>
        <div className="flex space-x-4 text-sm text-muted-foreground">
          <div className="flex items-center">
            <CircleIcon className="mr-1 h-3 w-3 fill-sky-400 text-sky-400" />
            Mobikwik
          </div>
          <div>Expires on 30/12/10</div>
        </div>
      </CardContent>
    </Card>
  );
}

Now we created our Flightcard component in the above code. It imports the CircleIcon component from the @radix-ui/react-icons library and various UI components from a local file called ./ui/card. The Flightcard component returns a Card component that contains a CardHeader and a CardContent. The CardHeader displays the title “Indigo offers” and a description that says “Get upto 10% off on indigo app bookings”. The CardContent shows that the offer is in collaboration with Mobikwik and that it expires on December 30, 2010. There is also a small blue circle icon next to the word “Mobikwik”. This component could be used to display information about an offer from Indigo airlines in a visually appealing way.

Think of it like a fun little postcard that tells you about a cool discount you can get if you book your flight through the Indigo app and pay with Mobikwik! Who doesn’t love saving money on travel? 😊

Now let's build our second React component called DatePickerWithRange.

Filename: components/DatePickerWithRange.tsx

import * as React from "react";
import { CalendarIcon } from "@radix-ui/react-icons";
import { addDays, format } from "date-fns";
import { DateRange } from "react-day-picker";

import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";

export function DatePickerWithRange({
  className,
}: React.HTMLAttributes<HTMLDivElement>) {
  const [date, setDate] = React.useState<DateRange | undefined>({
    from: new Date(2022, 0, 20),
    to: addDays(new Date(2022, 0, 20), 20),
  });

  return (
    <div className={cn("grid gap-2", className)}>
      <Popover>
        <PopoverTrigger asChild>
          <Button
            id="date"
            variant={"outline"}
            className={cn(
              "w-[300px] h-[50px] p-1 justify-start text-left",
              !date && "text-muted-foreground"
            )}
          >
            <CalendarIcon className="mr-2 h-4 w-4" />
            {date?.from ? (
              date.to ? (
                <>
                  {format(date.from, "LLL dd, y")} -{" "}
                  {format(date.to, "LLL dd, y")}
                </>
              ) : (
                format(date.from, "LLL dd, y")
              )
            ) : (
              <span>Pick a date</span>
            )}
          </Button>
        </PopoverTrigger>
        <PopoverContent className="w-auto p-5" align="start">
          <Calendar
            initialFocus
            mode="range"
            defaultMonth={date?.from}
            selected={date}
            onSelect={setDate}
            numberOfMonths={2}
          />
        </PopoverContent>
      </Popover>
    </div>
  );
}

It imports various components and functions from external libraries and local files. The DatePickerWithRange component takes a className prop and returns a div element that contains a Popover component. The Popover has a trigger that displays a Button with an icon and text that shows the currently selected date range or the text “Pick a date” if no date range is selected. When the button is clicked, the PopoverContent is displayed, which contains a Calendar component that allows the user to select a date range.

This component could be used to display a date picker with a range selection feature. The user can click on the button to open the calendar and select a start and end date for their desired date range. It’s like having your own personal calendar assistant right on your screen! 😊

Now let's build our third React component called Search.

Filename: components/Search.tsx

import * as React from "react";
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";

import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
} from "@/components/ui/command";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";

const frameworks = [
  {
    value: "mumbai",
    label: "mumbai",
  },
  {
    value: "delhi",
    label: "delhi",
  },
  {
    value: "chandigarh",
    label: "chandigarh",
  }
];

export function Search() {
  const [open, setOpen] = React.useState(false);
  const [value, setValue] = React.useState("");

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          role="combobox"
          aria-expanded={open}
          className="w-[300px] h-[50px] p-4 justify-between"
        >
          {value
            ? frameworks.find((framework) => framework.value === value)?.label
            : "From"}
          <CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-[200px] p-0">
        <Command>
          <CommandInput placeholder="Search framework..." className="h-9" />
          <CommandEmpty>No framework found.</CommandEmpty>
          <CommandGroup>
            {frameworks.map((framework) => (
              <CommandItem
                key={framework.value}
                onSelect={(currentValue) => {
                  setValue(currentValue === value ? "" : currentValue);
                  setOpen(false);
                }}
              >
                {framework.label}
                <CheckIcon
                  className={cn(
                    "ml-auto h-4 w-4",
                    value === framework.value ? "opacity-100" : "opacity-0"
                  )}
                />
              </CommandItem>
            ))}
          </CommandGroup>
        </Command>
      </PopoverContent>
    </Popover>
  );
}

It imports various components and functions from external libraries and local files. The Search component maintains state for whether the search popover is open and the current search value. It returns a Popover component that contains a trigger and content. The trigger is a Button that displays the currently selected search value or the text “From” if no value is selected. When the button is clicked, the PopoverContent is displayed, which contains a Command component that allows the user to search for and select a framework from a predefined list.

This component could be used to display a search field with a dropdown list of options. The user can click on the button to open the list and select an option or enter text to search for a specific framework. It’s like having your own personal search assistant right on your screen! 😊

Filename: components/Content.tsx

import Link from "next/link";
import React from "react";
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuIndicator,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from "@/components/ui/navigation-menu";
import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
import { Search } from "./Search";
import { Button } from "@/components/ui/button";
import { DatePickerWithRange } from "./Datepicker";
import { Flightcard } from "./Flightcard";

const Content = () => {
  return (
    <div className="flex flex-col gap-4">
      <div>
        <Link href="home">Home</Link>
        <span><</span>
        <span>Flight Booking</span>
      </div>
      <h1 className="text-[40px]">Flight Booking at Low-cost</h1>
      <NavigationMenu>
        <NavigationMenuList>
          <NavigationMenuItem>
            <NavigationMenuTrigger>
              <span className="text-[20px]">➡️One Way</span>
            </NavigationMenuTrigger>
            <NavigationMenuContent>
              <NavigationMenuLink className={navigationMenuTriggerStyle()}>
                <span className="text-[20px]">🔄Round Trip</span>
              </NavigationMenuLink>
            </NavigationMenuContent>
          </NavigationMenuItem>
        </NavigationMenuList>
      </NavigationMenu>
      <div className="flex flex-row gap-5 justify-center items-center">
        <Search />
        <span className="">🔄</span>
        <Search />
        <DatePickerWithRange />
        <button className="bg-black p-3">Search Flight</button>
      </div>
      <div className="flex flex-row gap-5 p-10 justify-center items-center">
        <Flightcard />
        <Flightcard />
        <Flightcard />
        <Flightcard />
      </div>
    </div>
  );
};

export default Content;

Now we import various components from external libraries and local files, including Link from next/link, NavigationMenu and related components from @/components/ui/navigation-menu, Search, DatePickerWithRange, and Flightcard from local files to create the content section for our page.

The Content component returns a div element that contains several child elements. The first child is another div that displays a link to the home page, a left arrow symbol, and the text “Flight Booking”. The second child is an h1 element that displays the text “Flight Booking at Low-cost”. The third child is a NavigationMenu component that contains a list with one item. The item has a trigger that displays the text “➡️One Way” and a content area that displays a link with the text “🔄Round Trip”.

The fourth child is another div that contains several elements arranged in a row. These elements include two Search components, a symbol, a DatePickerWithRange component, and a button with the text “Search Flight”. The fifth child is yet another div that contains four Flightcard components arranged in a row.

This component could be used to display a page for booking flights. It includes options for selecting one-way or round-trip flights, entering search criteria, and viewing flight offers. It’s like having your own personal travel agent right on your screen! 😊The code you provided is for a React component called Content. It imports various components from external libraries and local files, including Link from next/link, NavigationMenu and related components from @/components/ui/navigation-menu, Search, DatePickerWithRange, and Flightcard from local files.

The Content component returns a div element that contains several child elements. The first child is another div that displays a link to the home page, a left arrow symbol, and the text “Flight Booking”. The second child is an h1 element that displays the text “Flight Booking at Low-cost”. The third child is a NavigationMenu component that contains a list with one item. The item has a trigger that displays the text “➡️One Way” and a content area that displays a link with the text “🔄Round Trip”.

The fourth child is another div that contains several elements arranged in a row. These elements include two Search components, a symbol, a DatePickerWithRange component, and a button with the text “Search Flight”. The fifth child is yet another div that contains four Flightcard components arranged in a row.

This component could be used to display a page for booking flights. It includes options for selecting one-way or round-trip flights, entering search criteria, and viewing flight offers. It’s like having your own personal travel agent right on your screen! 😊

And that’s a wrap! We’ve successfully built our page using Next.js and Shadcn UI components. The final version looks amazing, and we couldn’t have done it without the power of these awesome tools. It’s like we’ve created our own masterpiece, and now it’s time to sit back and admire our work. Great job, everyone! 🎉🎉🎉