ComponentsDocs

Purchase the template here if you want all the components and the starter guide.

Get some of the same components used on this site in your own project.

/Sidebar.js

1"use client";
2
3import { useState, useEffect } from "react";
4import { usePathname } from "next/navigation";
5import Image from "next/image";
6import Link from "next/link";
7import config from "@/config";
8import Icons from "@/components/Icons";
9import logo from "@/app/icon.png";
10
11const docs = [
12  {
13    title: "Getting Started",
14    href: "/docs/getting-started",
15    icon: "Play",
16  },
17  {
18    slug: "tutorials",
19    title: "Tutorials",
20    href: "/docs/tutorials",
21    icon: "Book",
22    sub: [
23      {
24        title: "Ship in 5 minutes",
25        href: "/docs/tutorials/ship-in-5-minutes",
26      },
27    ],
28  },
29];
30
31const external = [
32    { href: "https://shipfa.st/?via=shipfast_guide", text: "Buy ShipFast", icon: "ShoppingCart", target: "_blank" },
33];
34
35const Sidebar = () => {
36  const [isOpen, setIsOpen] = useState(true);
37  const pathname = usePathname() ?? "/";
38
39  const toggleSidebar = () => {
40    setIsOpen(!isOpen);
41  };
42
43  useEffect(() => {
44    setIsOpen(true);
45  }, [pathname]);
46
47  return (
48    <>
49      <div className={`fixed z-50 sm:hidden ${isOpen ? "left-5 top-5" : "right-5 top-5"}`}>
50        <button onClick={toggleSidebar} className="btn btn-sm btn-ghost btn-square bg-base-100">
51          <Icons name="Menu" width="20" />
52        </button>
53      </div>
54      <div
55        className={`overflow-y-auto overflow-x-visible pb-12 transform ${isOpen ? "-translate-x-full" : "w-full translate-x-0"
56          } fixed z-40 flex h-full flex-col  border-r border-base-content/10 bg-base-100 p-4 transition-all sm:w-64 sm:translate-x-0`}
57      >
58        <div className="grid gap-2 py-4">
59          <div className="flex items-center space-x-2 rounded-lg">
60            <Link
61              href="/"
62              className="rounded-lg p-2 hover:bg-hover cursor-pointer"
63            >
64              <Image
65                src={logo}
66                alt={`${config.appName} logo`}
67                loading="lazy"
68                width={24}
69                height={24}
70                decoding="async"
71                data-nimg="1"
72                className="scale-110"
73                style={{ color: "transparent" }}
74              />
75            </Link>
76          </div>
77          {docs.map((item, index) => (
78            <div key={index}>
79              <NavLink href={item.href} isActive={pathname === item.href}>
80                <Icons name={item.icon} />
81                <span className="text-sm font-semibold">{item.title}</span>
82              </NavLink>
83              {item.sub && (
84                <div className="my-1">
85                  {item.sub.map((subItem, subIndex) => (
86                    <SubLink
87                      key={subIndex}
88                      href={subItem.href}
89                      isActive={pathname === subItem.href}
90                    >
91                      <span>{subItem.title}</span>
92                    </SubLink>
93                  ))}
94                </div>
95              )}
96            </div>
97          ))}
98        </div>
99        <div className="mb-4 border-t border-base-content/10"></div>
100        <ExternalLinks />
101      </div>
102    </>
103  );
104};
105
106const NavLink = ({ href, isActive, children }) => {
107  return (
108    <Link
109      className={`flex items-center space-x-3 ${isActive ? "text-base-content" : "text-base-content/50"
110        } rounded-lg px-2 py-1.5 transition-all duration-150 ease-in-out hover:text-base-content active:text-base-content`}
111      href={href}
112    >
113      {children}
114    </Link>
115  );
116};
117
118const SubLink = ({ href, isActive, children }) => {
119  return (
120    <Link
121      className={`ml-[17px] pl-[21px] flex items-center border-l text-sm ${isActive
122          ? "text-base-content border-base-content"
123          : "text-base-content/50 border-l-base-content/10 hover:border-l-base-content/50"
124        } py-1.5 transition-all duration-150 ease-in-out hover:text-base-content active:text-base-content active:border-l-base-content space-x-3`}
125      href={href}
126    >
127      {children}
128    </Link>
129  );
130};
131
132const ExternalLinks = () => {
133  return (
134    <div className="grid gap-1">
135      {external.map((link, index) => (
136        <Link
137          key={index}
138          href={link.href}
139          target={link.target}
140          className="flex items-center justify-between rounded-lg px-2 py-1.5 transition-all duration-150 ease-in-out text-base-content/60 hover:bg-hover hover:text-base-content active:bg-active active:text-base-content"
141        >
142          <div className="flex items-center space-x-3">
143            <Icons name={link.icon} />
144            <span className="text-sm font-medium">{link.text}</span>
145          </div>
146          <p>ā†—</p>
147        </Link>
148      ))}
149    </div>
150  );
151};
152
153export default Sidebar; 

/layout.js

1<Suspense>
2  <Sidebar />
3</Suspense>

Code Block

Install

Terminal

1npm i @stianlarsen/copy-to-clipboard

Code

/CodeBlock.js

1"use client";
2
3import { default as SyntaxHighlighter } from "react-syntax-highlighter";
4import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/hljs';
5import { copy } from "@stianlarsen/copy-to-clipboard";
6import Icons from "./Icons";
7import toast from "react-hot-toast";
8
9const CodeBlock = ({ children, lang, title }) => {
10    return (
11        <div className="text-sm bg-base-100 rounded-lg overflow-hidden border border-base-content/20">
12            <div className="px-4 py-2 flex justify-between items-center border-b border-base-content/20">
13                <p className="text-base-content/60">{title}</p>
14                <button
15                    className="btn btn-sm btn-square text-base-content btn-ghost"
16                    onClick={() => copy(children, toast.success("Copied to clipboard!"))}
17                >
18                    <Icons name="Copy" />
19                </button>
20            </div>
21            <SyntaxHighlighter
22                style={a11yDark}
23                language={lang}
24                showLineNumbers={true}
25                useInlineStyles={true}
26                wrapLines={true}
27                lineNumberStyle={{ color: "#7d7d7d" }}
28                PreTag={({ children }) => <pre style={{ display: "block", overflowX: "auto", background: "#2b2b2b", color: "#f8f8f2", padding: "1.25rem" }}>{children}</pre>}
29            >
30                {children}
31            </SyntaxHighlighter>
32        </div>
33    );
34};
35
36export default CodeBlock;

Usage

/page.js

1<CodeBlock lang="shell" title="Terminal">{`npm i next`}</CodeBlock>

Icons

Install

Terminal

1npm i lucide-react

Code

/components/Icons.js

1import { icons } from 'lucide-react';
2
3const Icons = ({ name, width = 18, className = "" }) => {
4    const LucideIcon = icons[name];
5
6    return <LucideIcon width={width} height={24} className={className} />;
7};
8
9export default Icons;

Usage

/page.js

1<Icon name="Menu" width="20" /> // The menu button icon for open and close the sidebar
2<Icon name={item.icon} /> // The sidebar icons for each category
3<Icon name="Copy" /> // The copy icon for the code block

Headless UI 1.7

/Search.js

1"use client";
2
3import React, { useState } from "react";
4import { useRouter } from "next/navigation";
5import { Combobox } from "@headlessui/react";
6
7const docs = [
8  {
9    slug: "get-started",
10    title: "Get Started",
11    href: "/docs/get-started",
12  },
13  {
14    slug: "tutorials",
15    title: "Tutorials",
16    href: "/docs/tutorials",
17    sub: [
18      {
19        slug: "ship-in-5-minutes",
20        title: "Ship In 5 Minutes",
21        href: "/docs/tutorials/ship-in-5-minutes",
22      },
23    ],
24  },
25];
26
27const Search = () => {
28  const { push } = useRouter();
29  const [options, setOptions] = useState([]);
30
31  const handleInputChange = (e) => {
32    filterOptions(e.target.value);
33  };
34
35  const filterOptions = (query) => {
36    if (query == undefined || query == "") {
37      setOptions([]); // Clear options if query is empty or undefined
38      return;
39    }
40
41    const filteredOptions = [];
42
43    const filterRecursively = (docsArray, isSubOption = false) => {
44      docsArray.forEach((option) => {
45        if (
46          option.title.toLowerCase().includes(query.toLowerCase()) ||
47          option.slug.toLowerCase().includes(query.toLowerCase())
48        ) {
49          filteredOptions.push({ ...option, isSubOption });
50        }
51        if (option.sub) {
52          filterRecursively(option.sub, true);
53        }
54      });
55    };
56
57    filterRecursively(docs);
58
59    setOptions(filteredOptions);
60  };
61
62  const handleSelectionChange = (change) => {
63    if (change == undefined || change == "") return;
64    const selected = options.find((option) => option.slug == change);
65    push(selected.href);
66  };
67
68  return (
69    <div className="z-50 max-md:hidden fixed top-6 right-6">
70      <div className="items-center justify-start gap-4 lg:gap-12">
71        <div className="w-72 z-10">
72          <div className="relative">
73            <div className="relative w-full cursor-default rounded-lg bg-base-100 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-base-100/20 focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:text-sm group">
74              <Combobox onChange={handleSelectionChange}>
75                <Combobox.Input
76                  onChange={handleInputChange}
77                  className="bg-base-100 w-full py-3 pl-10 pr-3 text-sm leading-5 text-base-content border border-base-content/20 rounded-lg focus:ring-0 focus:outline-primary"
78                  placeholder="Search"
79                />
80                <Combobox.Button
81                  data-headlessui-state=""
82                  className="absolute inset-y-0 left-0 flex items-center pl-3 text-sm"
83                >
84                  <svg
85                    xmlns="http://www.w3.org/2000/svg"
86                    width="18"
87                    height="24"
88                    viewBox="0 0 24 24"
89                    fill="none"
90                    stroke="currentColor"
91                    strokeWidth="2"
92                    strokeLinecap="round"
93                    strokeLinejoin="round"
94                    className="text-base-content/80"
95                  >
96                    <circle cx="11" cy="11" r="8"></circle>
97                    <path d="m21 21-4.3-4.3"></path>
98                  </svg>
99                </Combobox.Button>
100                <Combobox.Options
101                  as="ul"
102                  className="z-50 absolute mt-1 max-h-96 w-full overflow-auto rounded-lg bg-base-100 py-1 text-base shadow-lg ring-1 ring-base-content ring-opacity-20 focus:outline-none sm:text-sm"
103                >
104                  {options.map((option) => (
105                    <Combobox.Option
106                      as="li"
107                      key={option.slug}
108                      value={option.slug}
109                      className="relative cursor-pointer select-none py-2 px-4 text-base-content-secondary data-[focus]:bg-[#4a4949]"
110                    >
111                      <p
112                        className={
113                          option.isSubOption
114                            ? "border-l border-base-content/20 pl-3 ml-3 truncate font-normal"
115                            : "truncate font-normal"
116                        }
117                      >
118                        {option.title}
119                      </p>
120                    </Combobox.Option>
121                  ))}
122                </Combobox.Options>
123              </Combobox>
124            </div>
125          </div>
126        </div>
127      </div>
128    </div>
129  );
130};
131
132export default Search;

Headless UI 2.0

/Search.js

1"use client";
2
3import React, { useState } from "react";
4import { useRouter } from "next/navigation";
5import {
6  Combobox,
7  ComboboxInput,
8  ComboboxButton,
9  ComboboxOptions,
10  ComboboxOption,
11} from "@headlessui/react";
12
13const docs = [
14  {
15    slug: "get-started",
16    title: "Get Started",
17    href: "/docs/get-started",
18  },
19  {
20    slug: "tutorials",
21    title: "Tutorials",
22    href: "/docs/tutorials",
23    sub: [
24      {
25        slug: "ship-in-5-minutes",
26        title: "Ship In 5 Minutes",
27        href: "/docs/tutorials/ship-in-5-minutes",
28      },
29    ],
30  },
31];
32
33const Search = () => {
34  const { push } = useRouter();
35  const [options, setOptions] = useState([]);
36
37  const handleInputChange = (e) => {
38    filterOptions(e.target.value);
39  };
40
41  const filterOptions = (query) => {
42    if (query == undefined || query == "") {
43      setOptions([]); // Clear options if query is empty or undefined
44      return;
45    }
46
47    const filteredOptions = [];
48
49    const filterRecursively = (docsArray, isSubOption = false) => {
50      docsArray.forEach((option) => {
51        if (
52          option.title.toLowerCase().includes(query.toLowerCase()) ||
53          option.slug.toLowerCase().includes(query.toLowerCase())
54        ) {
55          filteredOptions.push({ ...option, isSubOption });
56        }
57        if (option.sub) {
58          filterRecursively(option.sub, true);
59        }
60      });
61    };
62
63    filterRecursively(docs);
64
65    setOptions(filteredOptions);
66  };
67
68  const handleSelectionChange = (change) => {
69    if (change == undefined || change == "") return;
70    const selected = options.find((option) => option.slug == change);
71    push(selected.href);
72  };
73
74  return (
75    <div className="z-50 max-md:hidden fixed top-6 right-6">
76      <div className="items-center justify-start gap-4 lg:gap-12">
77        <div className="w-72 z-10">
78          <div className="relative">
79            <div className="relative w-full cursor-default rounded-lg bg-base-100 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-base-100/20 focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:text-sm group">
80              <Combobox onChange={handleSelectionChange}>
81                <ComboboxInput
82                  onChange={handleInputChange}
83                  className="bg-base-100 w-full py-3 pl-10 pr-3 text-sm leading-5 text-base-content border border-base-content/20 rounded-lg focus:ring-0 focus:outline-primary"
84                  placeholder="Search"
85                />
86                <ComboboxButton
87                  data-headlessui-state=""
88                  className="absolute inset-y-0 left-0 flex items-center pl-3 text-sm"
89                >
90                  <svg
91                    xmlns="http://www.w3.org/2000/svg"
92                    width="18"
93                    height="24"
94                    viewBox="0 0 24 24"
95                    fill="none"
96                    stroke="currentColor"
97                    strokeWidth="2"
98                    strokeLinecap="round"
99                    strokeLinejoin="round"
100                    className="text-base-content/80"
101                  >
102                    <circle cx="11" cy="11" r="8"></circle>
103                    <path d="m21 21-4.3-4.3"></path>
104                  </svg>
105                </ComboboxButton>
106                <ComboboxOptions
107                  as="ul"
108                  className="z-50 absolute mt-1 max-h-96 w-full overflow-auto rounded-lg bg-base-100 py-1 text-base shadow-lg ring-1 ring-base-content ring-opacity-20 focus:outline-none sm:text-sm"
109                >
110                  {options.map((option) => (
111                    <ComboboxOption
112                      as="li"
113                      key={option.slug}
114                      value={option.slug}
115                      className="relative cursor-pointer select-none py-2 px-4 text-base-content-secondary data-[focus]:bg-[#4a4949]"
116                    >
117                      <p
118                        className={
119                          option.isSubOption
120                            ? "border-l border-base-content/20 pl-3 ml-3 truncate font-normal"
121                            : "truncate font-normal"
122                        }
123                      >
124                        {option.title}
125                      </p>
126                    </ComboboxOption>
127                  ))}
128                </ComboboxOptions>
129              </Combobox>
130            </div>
131          </div>
132        </div>
133      </div>
134    </div>
135  );
136};
137
138export default Search;

Usage

/page.js

1<Suspense>
2  <Sidebar />
3  <Search />
4</Suspense>