Let's build a personal website with react, Vite and Tailwind CSS
Here's a step-by-step guide on how to build a website with react and tailwind CSS.
Project Setup
First, make sure you have Node.js and NPM (Node Package Manager) installed on your machine. You can check if you have these by running the following commands in your terminal:
node -v
npm -v
Next, create a new directory for your project and navigate to it in your terminal. Run the following command to create a new React app using create vite@latest:
npm create vite@latest
Vite will offer a wide array of options to use as a UI framework/library like Vue.JS, Svelte and React. Let's use React in this example.
Install project dependencies
npm install tailwindcss yup formik clsx react-router-dom @radix-ui/react-alert-dialog
Next, create a configuration file for Tailwind CSS. Run the following command to generate a default configuration file:
npx tailwindcss init
Now, let's create a CSS file for our Tailwind styles. Create a new file called tailwind.css
in the src
directory of your project and add the following line to it:
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
Creating Routes
Creating a router helps us navigate to different pages inside the project. For this case, we need a home page, and a dynamic project detail page. Before working with the router, let first configure it in the main.tsx page
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { BrowserRouter } from 'react-router-dom'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
)
Next, create a router folder with a Router.tsx file
import React from 'react'
import { HomePage, ProjectPage } from '../pages'
import { Route, Routes } from 'react-router-dom'
function Router() {
return (
<Routes>
<Route path="/" element={<HomePage />}></Route>
<Route path="/projects/:slug" element={<ProjectPage />}></Route>
</Routes>
)
}
export default Router
Finally, add the router component as a child of the App component in app.tsx
import { Router } from './router'
import './App.css'
import { Layout, HomePage } from './components'
import { AlertProvider } from './context/AlerctContext'
function App() {
return (
<AlertProvider>
<Layout>
<Router />
</Layout>
</AlertProvider>
)
}
export default App
Creating the project Layout
The layout component will wrap our App and add a footer and a nav bar to it. This helps us avoid adding these components to every page, as w know all our pages will display them.
import React, { ReactNode } from 'react'
import Navbar from './Navbar'
import Footer from './Footer'
import { Container } from './Container'
function Layout({ children }: { children: ReactNode }) {
return (
<div>
<Navbar />
<Container>{children}</Container>
<Footer />
</div>
)
}
export default Layout
Creating the navigation bar
- Create a Navbar.tsx in the components directory.
- Add the Radix icons package to use the beautifully designed icon pack
npm install @radix-ui/react-icons
Below is the full code for the navbar component.
import React, { useEffect, useRef, useState } from 'react'
import {
GitHubLogoIcon,
LinkedInLogoIcon,
InstagramLogoIcon,
EnvelopeClosedIcon,
RocketIcon,
} from '@radix-ui/react-icons'
import cn from 'clsx'
function Navbar() {
const [yOffset, setYOffset] = useState(window.pageYOffset)
const [visible, setVisible] = useState(true)
const handleClickScroll = (sectionId: string) => {
const element = document.getElementById(sectionId)
if (element) {
// 👇 Will scroll smoothly to the top of the next section
element.scrollIntoView({ behavior: 'smooth' })
}
}
useEffect(() => {
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
})
function handleScroll() {
const currentYOffset = window.pageYOffset
const visible = yOffset > currentYOffset
setYOffset(currentYOffset)
setVisible(visible)
}
return (
<nav
className={cn('sticky top-0 ease-in duration-300', {
'-top-16': !visible,
})}
>
<ul className="flex gap-12 justify-center items-center p-4 backdrop-blur-lg border-b bg-white bg-opacity-60 border-gray-200 md-gap-16">
<a
className="flex gap-4 justify-center items-center"
href="https://github.com/AchrafGarai"
>
<GitHubLogoIcon width={20} height={20} />
<li className="hidden md:block">Github</li>
</a>
<a
className="flex gap-4 justify-center items-center"
href="https://www.linkedin.com/in/achraf-garai/"
>
<LinkedInLogoIcon width={20} height={20} />
<li className="hidden md:block">LinkedIn</li>
</a>
<a
className="flex gap-4 justify-center items-center"
href="https://www.instagram.com/achraf_garai/"
>
<InstagramLogoIcon width={20} height={20} />
<li className="hidden md:block">Instagram</li>
</a>
<button
onClick={() => handleClickScroll('projectsSection')}
className="flex gap-4 justify-center items-center"
>
<RocketIcon width={20} height={20} />
<p className="hidden md:block">My Projects</p>
</button>
<button
onClick={() => handleClickScroll('contactSection')}
className="flex gap-4 justify-center items-center"
>
<EnvelopeClosedIcon width={20} height={20} />
<p className="hidden md:block">Contact me</p>
</button>
</ul>
</nav>
)
}
export default Navbar
Creating pages
Next, let's create a Pages folder that contains our pages, the Homepage.tsx, and the HrojectDetailsPage.tsx. Each of these components represents a page that is defined in the router.
Creating the home page component
On the home page lets create and import three components for our Hero, projects and contact sections.
import LandingSection from '../components/LandingSection'
import ProjectsSection from '../components/ProjectsSection'
import ContactSection from '../components/ContactSection'
import { Container } from '../components/Container'
import { Helmet } from 'react-helmet'
function HomePage() {
return (
<Container>
<Helmet>
<title>Welcome to my blog 🔥</title>
</Helmet>
<LandingSection />
<ProjectsSection />
<ContactSection />
</Container>
)
}
export default HomePage
Let's create a components folder to hold all our non-page components.
Landing section
Next in the components folder, create a HeroSection.tsx file
import React from 'react'
const greeting = 'Hello, I am Achraf!'
const bio1 =
'A frontend developer and designer. I am also passionate about code and UI'
const bio2 = 'specialised in React'
function LandingSection() {
return (
<div className="text-center flex flex-col items-center justify-center gap-8 ">
<img
className=" object-cover my-16 w-64 h-96 rounded-full"
src="/profile.jpg"
alt="Achraf Garai"
/>
<h1 className="my-4 text-5xl font-serif">{greeting}</h1>
<p className="text-xl">{bio1}</p>
</div>
)
}
export default LandingSection
Projects Section
The projects section loads a JavaScript object contains the project details in the Data folder (Full Code available in the GitHub repo). The component renders each component as a card in a Grid layout.
import { projects } from '../data/posts'
import ProjectCard from './ProjectCard'
function ProjectsSection() {
return (
<div
id="projectsSection"
className="my-8 py-8 grid grid-cols-1 gap-8 md:grid-cols-2"
>
{projects.map((project) => (
<ProjectCard
key={project.title}
title={project.title}
image={project.image}
description={project.description}
slug={project.slug}
/>
))}
</div>
)
}
export default ProjectsSection
For each project, we render a project card components with is added in ProjectCard.tsx
import { Project } from '../data/posts'
import { Link } from 'react-router-dom'
function ProjectCard({ title, description, image, slug }: Project) {
return (
<Link to={`/projects/${slug}`}>
<div className="border border-slate-100 rounded-lg overflow-hidden">
<img src={image} className=" object-cover w-full h-96 mb-4" alt="" />
<div className="p-4">
<p className="mb-4 text-2xl font-bold text-gray-800">{title}</p>
<p className="text-lg text-gray-500">{description}</p>
</div>
</div>
</Link>
)
}
export default ProjectCard
Contact section
First, make sure you have the Formik and Yup libraries installed in your project (see project setup section). Next, import the Formik
and Yup
libraries in your component file:
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
Now, let's define a validation schema for our form using Yup. This will define the rules for each form field, such as whether it is required or has a minimum length.
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { AlertType } from '../context/AlerctContext';
import * as Yup from 'yup';
import { useAlertContext } from '../context/AlerctContext';
const SignupSchema = Yup.object().shape({
name: Yup.string()
.min(2, 'Name is too Short!')
.max(50, 'Too Long!')
.required('This field is required!'),
message: Yup.string()
.min(2, 'Your message is too short!')
.max(140, 'Too Long!')
.required('This field is required!'),
email: Yup.string()
.email('Invalid email')
.required('This field is required!'),
enquiry: Yup
.string()
.oneOf(["freelance", "open source", "other"])
.required("This field is required!"),
})
Finally, we add the Contact From components (see the GitHub repo for the full code)
Project Details
The components use UseParams from React to take the URL path for the project (example /projects/react-state). Then we fetch the project with the correct title from our data object.
import { useParams } from 'react-router-dom'
import BackButton from '../components/BackButton'
import ContactSection from '../components/ContactSection'
import { Container } from '../components/Container'
import { projects } from '../data/posts'
import NotFound from './NotFound'
import { Helmet } from 'react-helmet'
function ProjectPage() {
const params = useParams()
const slug = params.slug || ''
const project = projects.find((p) => {
return p.slug === slug
})
if (project == undefined) {
return <NotFound />
} else {
return (
<div>
<Helmet>
<title>{project.title}</title>
</Helmet>
<Container variant="blog">
<BackButton to="/" />
<img
src={project.image}
alt={project.title}
className=" object-cover w-full h-96 rounded-2xl"
/>
<section>
<h1 className="py-16 text-4xl font-bold text-slate-800 text-center">
{project.title}
</h1>
</section>
<section></section>
<section>{project.description}</section>
</Container>
<div className="my-24">
<ContactSection />
</div>
</div>
)
}
}
export default ProjectPage
Click the button below to grab the full project files and test the project.