Overview
The Projects component displays a grid of project cards, each featuring an image, title, tech stack, description, and a link to view the full case study. It dynamically renders from project data.
This component is data-driven and pulls from a centralized projects array, making it easy to add or update portfolio items.
Component Structure
Location : src/components/Projects/Projects.jsx
Styles : src/components/Projects/Projects.module.css
Data Source : src/data/projects.js
Dependencies : react-router-dom for navigation
Implementation
Basic Usage
import Projects from './components/Projects/Projects' ;
export default function App () {
return (
< main >
< Projects />
</ main >
);
}
Full Component Code
import { Link } from 'react-router-dom' ;
import { projects } from '../../data/projects' ;
import styles from './Projects.module.css' ;
function ProjectCard ({ project }) {
return (
< article className = { styles . card } >
< div className = { styles . imgWrap } >
< div className = { styles . imgOverlay } />
< img
src = { project . img }
alt = { project . imgAlt }
className = { styles . cardImg }
loading = "lazy"
/>
</ div >
< div className = { styles . cardBody } >
< div className = { styles . cardHeader } >
< div >
< h3 className = { styles . cardTitle } > { project . title } </ h3 >
< div className = { styles . techContainer } >
{ project . techList . map (( tech , index ) => (
< span key = { ` ${ tech } - ${ index } ` } className = { styles . cardTech } >
{ tech }
</ span >
)) }
</ div >
</ div >
</ div >
< p className = { styles . cardDesc } > { project . desc } </ p >
< Link to = { `/project/ ${ project . id } ` } className = { styles . caseStudyBtn } >
View Case Study
< span className = "material-icons" > arrow_forward </ span >
</ Link >
</ div >
</ article >
);
}
export default function Projects () {
return (
< section id = "projects" className = { styles . section } >
< div className = "container" >
< div className = { styles . sectionHeader } >
< h2 className = { styles . sectionTitle } > SELECTED WORKS </ h2 >
< span className = { styles . sectionDate } > /// 2022 - 2025 </ span >
</ div >
< div className = { styles . grid } >
{ projects . map (( p ) => (
< ProjectCard key = { p . id } project = { p } />
)) }
</ div >
</ div >
</ section >
);
}
Component Breakdown
Projects Section
The main component renders:
Section Header : Title and date range
Project Grid : Maps over projects array to render cards
< div className = { styles . sectionHeader } >
< h2 className = { styles . sectionTitle } > SELECTED WORKS </ h2 >
< span className = { styles . sectionDate } > /// 2022 - 2025 </ span >
</ div >
ProjectCard Component
Internal component that renders individual project cards with props:
Props :
project: Object containing project data (id, title, img, desc, techList, etc.)
ProjectCard Structure
< div className = { styles . imgWrap } >
< div className = { styles . imgOverlay } />
< img
src = { project . img }
alt = { project . imgAlt }
className = { styles . cardImg }
loading = "lazy"
/>
</ div >
Image wrapper with overlay effect and lazy loading for performance. < div className = { styles . cardBody } >
< div className = { styles . cardHeader } >
< h3 className = { styles . cardTitle } > { project . title } </ h3 >
< div className = { styles . techContainer } >
{ project . techList . map (( tech , index ) => (
< span key = { ` ${ tech } - ${ index } ` } className = { styles . cardTech } >
{ tech }
</ span >
)) }
</ div >
</ div >
< p className = { styles . cardDesc } > { project . desc } </ p >
< Link to = { `/project/ ${ project . id } ` } className = { styles . caseStudyBtn } >
View Case Study
< span className = "material-icons" > arrow_forward </ span >
</ Link >
</ div >
Contains title, tech tags, description, and CTA link.
Project Data Structure
Projects are imported from src/data/projects.js:
export const projects = [
{
id: 'chatverde' ,
title: 'CHAT VERDE' ,
tagline: 'CONVERSATIONAL APP' ,
tech: 'F# • .NET • NLP • C#' ,
techList: [ 'F#' , '.NET' , 'NLP' , 'C#' , 'Windows Forms' ],
desc: 'Optimized search performance handling natural language queries with under 100ms response times.' ,
fullDesc: 'A conversational console application...' ,
challenge: 'Finding products in a catalog...' ,
solution: 'I developed a search engine...' ,
stats: {
role: 'Software Engineer' ,
timeline: 'November 2025' ,
team: 'Estelí, Nicaragua'
},
img: '/ChatVerde.webp' ,
imgAlt: 'Console conversational interface'
},
// ... more projects
];
Required Fields
id (string): Unique identifier for routing
title (string): Project name
tagline (string): Short category label
techList (array): Array of technology strings
desc (string): Short description for card
fullDesc (string): Extended description for detail page
challenge (string): Problem statement
solution (string): Implementation approach
stats (object): role, timeline, team
img (string): Image path
imgAlt (string): Accessible image description
Tech tags are rendered dynamically from the techList array:
< div className = { styles . techContainer } >
{ project . techList . map (( tech , index ) => (
< span key = { ` ${ tech } - ${ index } ` } className = { styles . cardTech } >
{ tech }
</ span >
)) }
</ div >
Uses index in the key as a fallback since tech items may not be unique (though they typically are).
Navigation
Each card links to a detailed project page:
< Link to = { `/project/ ${ project . id } ` } className = { styles . caseStudyBtn } >
View Case Study
< span className = "material-icons" > arrow_forward </ span >
</ Link >
Routes to /project/[id] where the ProjectDetail component renders full case study.
CSS Module Styling
Grid Layout
.grid {
display : grid ;
grid-template-columns : repeat ( auto-fit , minmax ( 300 px , 1 fr ));
gap : 2 rem ;
}
Responsive grid that adapts column count based on available space.
Card Styling
Key classes:
.card: Main card container with border and shadow
.imgWrap: Image container with aspect ratio and overflow
.imgOverlay: Hover overlay effect
.cardBody: Content container with padding
.techContainer: Flex container for tech tags
.cardTech: Individual tech tag badges
.caseStudyBtn: CTA button with arrow icon
Image Optimization
< img
src = { project . img }
alt = { project . imgAlt }
className = { styles . cardImg }
loading = "lazy"
/>
Uses lazy loading since projects are below the fold, improving initial page load performance.
Customization
Add New Project
Edit src/data/projects.js:
export const projects = [
// ... existing projects
{
id: 'new-project' ,
title: 'NEW PROJECT' ,
tagline: 'WEB APP' ,
techList: [ 'React' , 'Node.js' , 'MongoDB' ],
desc: 'Short project description here.' ,
fullDesc: 'Extended description...' ,
challenge: 'The problem...' ,
solution: 'How you solved it...' ,
stats: {
role: 'Full Stack Developer' ,
timeline: 'Q1 2026' ,
team: '2 Developers'
},
img: '/new-project.webp' ,
imgAlt: 'Project screenshot'
}
];
Change Section Title
< h2 className = { styles . sectionTitle } > MY PORTFOLIO </ h2 >
< span className = { styles . sectionDate } > /// 2020 - PRESENT </ span >
Modify Grid Columns
Update CSS:
.grid {
grid-template-columns : repeat ( 3 , 1 fr ); /* Fixed 3 columns */
/* or */
grid-template-columns : repeat ( auto-fit , minmax ( 400 px , 1 fr )); /* Wider cards */
}
Custom Tech Tag Colors
Add conditional styling based on tech name:
< span
key = { ` ${ tech } - ${ index } ` }
className = { styles . cardTech }
data-tech = { tech . toLowerCase () }
>
{ tech }
</ span >
Then in CSS:
.cardTech [ data-tech = "react" ] {
background-color : #61dafb ;
color : #000 ;
}
.cardTech [ data-tech = "node.js" ] {
background-color : #68a063 ;
}
Accessibility
Semantic <section> element with unique id="projects"
Each card uses <article> for semantic structure
Proper heading hierarchy (h2 for section, h3 for cards)
Descriptive alt text for all images
Keyboard navigable links
Material Icons are decorative, not essential for understanding
Lazy loading images : loading="lazy" attribute
Efficient mapping : Single render pass with .map()
CSS Modules : Scoped styles, no runtime overhead
Memoization opportunity : Could wrap ProjectCard in React.memo() if project list is very large
ProjectDetail : Full case study page for individual projects
Navbar : Links to #projects section
Hero : Contains “VIEW PROJECTS” CTA