
Your portfolio is your digital business card, resume, and showcase all rolled into one. In today's competitive market, having a standout React portfolio can make the difference between landing your dream job or getting lost in the crowd.
What Makes a Great React Portfolio?
A compelling React portfolio should demonstrate your technical skills while providing an excellent user experience.
Key Elements
- Clean, Modern Design: Professional aesthetics that don't distract from your work
- Responsive Layout: Perfect display across all devices and screen sizes
- Performance Optimized: Fast loading times and smooth interactions
- Clear Navigation: Easy to find information and projects
- Call-to-Actions: Clear paths for potential employers or clients to contact you
Essential Portfolio Sections
Hero Section
Your hero section should immediately communicate who you are and what you do.
import { useState, useEffect } from "react";
export default function Hero() {
const [displayText, setDisplayText] = useState("");
const roles = ["Frontend Developer", "React Specialist", "UI/UX Designer"];
useEffect(() => {
// Typewriter effect implementation
let currentRoleIndex = 0;
let currentCharIndex = 0;
let isDeleting = false;
const typeWriter = () => {
const currentRole = roles[currentRoleIndex];
if (!isDeleting && currentCharIndex <= currentRole.length) {
setDisplayText(currentRole.slice(0, currentCharIndex));
currentCharIndex++;
} else if (isDeleting && currentCharIndex >= 0) {
setDisplayText(currentRole.slice(0, currentCharIndex));
currentCharIndex--;
}
if (currentCharIndex === currentRole.length) {
isDeleting = true;
setTimeout(typeWriter, 2000);
} else if (isDeleting && currentCharIndex === 0) {
isDeleting = false;
currentRoleIndex = (currentRoleIndex + 1) % roles.length;
setTimeout(typeWriter, 500);
} else {
setTimeout(typeWriter, isDeleting ? 100 : 150);
}
};
typeWriter();
}, []);
return (
<section className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-900 via-blue-900 to-purple-900">
<div className="text-center text-white px-4">
<h1 className="text-5xl md:text-7xl font-bold mb-4">
Hi, I'm <span className="text-blue-400">Sarah</span>
</h1>
<div className="text-2xl md:text-3xl mb-8 h-10">
I'm a{" "}
<span className="text-yellow-400 border-r-2 border-yellow-400">
{displayText}
</span>
</div>
<p className="text-xl mb-12 max-w-2xl mx-auto text-gray-300">
I create beautiful, functional, and user-centered digital experiences
that bring ideas to life through clean code and thoughtful design.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<button className="bg-blue-500 hover:bg-blue-600 px-8 py-4 rounded-full text-lg font-semibold transition-all transform hover:scale-105">
View My Work
</button>
<button className="border-2 border-white hover:bg-white hover:text-gray-900 px-8 py-4 rounded-full text-lg font-semibold transition-all">
Get In Touch
</button>
</div>
</div>
</section>
);
}
Projects Showcase
Display your best work with detailed case studies:
interface Project {
id: string;
title: string;
description: string;
image: string;
technologies: string[];
liveUrl?: string;
githubUrl?: string;
featured?: boolean;
}
export default function ProjectsGrid({ projects }: { projects: Project[] }) {
const [filter, setFilter] = useState("all");
const [filteredProjects, setFilteredProjects] = useState(projects);
const categories = ["all", "react", "nextjs", "mobile", "fullstack"];
useEffect(() => {
if (filter === "all") {
setFilteredProjects(projects);
} else {
setFilteredProjects(
projects.filter((project) =>
project.technologies.some((tech) =>
tech.toLowerCase().includes(filter.toLowerCase())
)
)
);
}
}, [filter, projects]);
return (
<section className="py-20 bg-gray-50">
<div className="container mx-auto px-4">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold mb-4">Featured Projects</h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
Here are some of my recent projects that showcase my skills and
experience
</p>
</div>
{/* Filter Buttons */}
<div className="flex flex-wrap justify-center gap-4 mb-12">
{categories.map((category) => (
<button
key={category}
onClick={() => setFilter(category)}
className={`px-6 py-3 rounded-full font-semibold transition-all ${
filter === category
? "bg-blue-500 text-white"
: "bg-white text-gray-600 hover:bg-gray-100"
}`}
>
{category.charAt(0).toUpperCase() + category.slice(1)}
</button>
))}
</div>
{/* Projects Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredProjects.map((project) => (
<div
key={project.id}
className="group relative bg-white rounded-xl overflow-hidden shadow-lg hover:shadow-xl transition-all duration-300"
>
<div className="relative overflow-hidden">
<img
src={project.image}
alt={project.title}
className="w-full h-48 object-cover group-hover:scale-110 transition-transform duration-300"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div className="absolute bottom-4 left-4 right-4 flex gap-2">
{project.liveUrl && (
<button className="bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-semibold hover:bg-blue-600">
Live Demo
</button>
)}
{project.githubUrl && (
<button className="bg-gray-800 text-white px-4 py-2 rounded-lg text-sm font-semibold hover:bg-gray-900">
GitHub
</button>
)}
</div>
</div>
</div>
<div className="p-6">
<div className="flex items-center justify-between mb-2">
<h3 className="text-xl font-semibold">{project.title}</h3>
{project.featured && (
<span className="bg-yellow-400 text-yellow-900 px-2 py-1 rounded-full text-xs font-semibold">
Featured
</span>
)}
</div>
<p className="text-gray-600 mb-4">{project.description}</p>
<div className="flex flex-wrap gap-2">
{project.technologies.map((tech) => (
<span
key={tech}
className="bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm"
>
{tech}
</span>
))}
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
}
Skills & Technologies
Showcase your technical expertise:
export default function SkillsSection() {
const skillCategories = [
{
title: "Frontend",
skills: [
{ name: "React", level: 95 },
{ name: "TypeScript", level: 90 },
{ name: "Next.js", level: 88 },
{ name: "Tailwind CSS", level: 92 },
],
},
{
title: "Backend",
skills: [
{ name: "Node.js", level: 85 },
{ name: "Python", level: 80 },
{ name: "PostgreSQL", level: 78 },
{ name: "GraphQL", level: 82 },
],
},
{
title: "Tools & Others",
skills: [
{ name: "Git", level: 93 },
{ name: "Docker", level: 75 },
{ name: "AWS", level: 70 },
{ name: "Figma", level: 88 },
],
},
];
return (
<section className="py-20 bg-white">
<div className="container mx-auto px-4">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold mb-4">Skills & Technologies</h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
Here's a breakdown of my technical skills and proficiency levels
</p>
</div>
<div className="grid md:grid-cols-3 gap-8">
{skillCategories.map((category, categoryIndex) => (
<div key={categoryIndex} className="bg-gray-50 rounded-xl p-6">
<h3 className="text-2xl font-semibold mb-6 text-center">
{category.title}
</h3>
<div className="space-y-4">
{category.skills.map((skill, skillIndex) => (
<div key={skillIndex}>
<div className="flex justify-between mb-2">
<span className="font-medium">{skill.name}</span>
<span className="text-sm text-gray-600">
{skill.level}%
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-3">
<div
className="bg-blue-500 h-3 rounded-full transition-all duration-1000 ease-out"
style={{ width: `${skill.level}%` }}
/>
</div>
</div>
))}
</div>
</div>
))}
</div>
</div>
</section>
);
}
Advanced Portfolio Features
Dark Mode Toggle
Implement theme switching for better user experience:
import { createContext, useContext, useEffect, useState } from "react";
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
useEffect(() => {
const stored = localStorage.getItem("theme");
if (stored) {
setTheme(stored);
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
setTheme("dark");
}
}, []);
useEffect(() => {
localStorage.setItem("theme", theme);
document.documentElement.classList.toggle("dark", theme === "dark");
}, [theme]);
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export const useTheme = () => useContext(ThemeContext);
// Usage in component
export default function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
className="p-2 rounded-lg bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700"
>
{theme === "dark" ? "☀️" : "🌙"}
</button>
);
}
Contact Form
Create an interactive contact form:
import { useState } from "react";
export default function ContactForm() {
const [formData, setFormData] = useState({
name: "",
email: "",
subject: "",
message: "",
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitStatus, setSubmitStatus] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
try {
const response = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
});
if (response.ok) {
setSubmitStatus("success");
setFormData({ name: "", email: "", subject: "", message: "" });
} else {
setSubmitStatus("error");
}
} catch (error) {
setSubmitStatus("error");
}
setIsSubmitting(false);
};
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
return (
<section className="py-20 bg-gray-900 text-white">
<div className="container mx-auto px-4 max-w-4xl">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold mb-4">Let's Work Together</h2>
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
Have a project in mind? I'd love to hear about it. Send me a message
and let's discuss!
</p>
</div>
<form onSubmit={handleSubmit} className="grid md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium mb-2">Name</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
required
className="w-full p-3 rounded-lg bg-gray-800 border border-gray-700 focus:border-blue-500 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Email</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
required
className="w-full p-3 rounded-lg bg-gray-800 border border-gray-700 focus:border-blue-500 focus:outline-none"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium mb-2">Subject</label>
<input
type="text"
name="subject"
value={formData.subject}
onChange={handleChange}
required
className="w-full p-3 rounded-lg bg-gray-800 border border-gray-700 focus:border-blue-500 focus:outline-none"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium mb-2">Message</label>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
required
rows={6}
className="w-full p-3 rounded-lg bg-gray-800 border border-gray-700 focus:border-blue-500 focus:outline-none resize-none"
/>
</div>
<div className="md:col-span-2">
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-blue-500 hover:bg-blue-600 disabled:bg-gray-600 px-8 py-4 rounded-lg text-lg font-semibold transition-all"
>
{isSubmitting ? "Sending..." : "Send Message"}
</button>
{submitStatus === "success" && (
<p className="text-green-400 text-center mt-4">
Message sent successfully!
</p>
)}
{submitStatus === "error" && (
<p className="text-red-400 text-center mt-4">
Failed to send message. Please try again.
</p>
)}
</div>
</form>
</div>
</section>
);
}
SEO and Performance Tips
Meta Tags and Open Graph
import Head from "next/head";
export default function SEOHead() {
return (
<Head>
<title>Sarah Johnson - Frontend Developer & React Specialist</title>
<meta
name="description"
content="Frontend Developer specializing in React, Next.js, and modern web technologies. Available for freelance projects and full-time opportunities."
/>
<meta property="og:title" content="Sarah Johnson - Frontend Developer" />
<meta
property="og:description"
content="Frontend Developer specializing in React and modern web technologies"
/>
<meta property="og:image" content="/og-image.jpg" />
<meta property="og:url" content="https://sarahjohnson.dev" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Sarah Johnson - Frontend Developer" />
<meta
name="twitter:description"
content="Frontend Developer specializing in React and modern web technologies"
/>
<meta name="twitter:image" content="/og-image.jpg" />
<link rel="canonical" href="https://sarahjohnson.dev" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
);
}
Conclusion
A well-crafted React portfolio is more than just a showcase of your work—it's a demonstration of your skills, attention to detail, and understanding of user experience. Focus on creating something that not only looks great but also performs well and provides genuine value to visitors.
Remember to:
- Keep your portfolio updated with recent projects
- Optimize for performance and SEO
- Make it mobile-friendly
- Include clear contact information
- Showcase your personality and unique value proposition
Your portfolio is an investment in your career—make it count!
Read more

21 Best React Landing Page Templates To Inspire Yours
Discover stunning React landing page templates and learn the design principles that make them convert visitors into customers.

21 Best Free React Components Libraries To Kickstart Projects
Discover the most powerful and popular React component libraries that will accelerate your development workflow and help you build stunning UIs.

23 NextJS Portfolio Template Examples For Design Inspiration
Explore the best NextJS portfolio templates and examples to showcase your work professionally and stand out from the competition.