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@latestVite 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 initNow, 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 RouterFinally, 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 AppCreating 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 LayoutCreating 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-iconsBelow 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 NavbarCreating 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 HomePageLet'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 LandingSectionProjects 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 ProjectsSectionFor 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 ProjectCardContact 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 ProjectPageClick the button below to grab the full project files and test the project.
