Back to All Articles
reacttailwindcsstypescriptfrontendportfolio

Building a High-Performance Dev Portfolio with React and Tailwind

Learn how to build a lightning-fast, accessible developer portfolio using React, TypeScript, and Tailwind CSS with smooth grid layouts.

2026-10-13
Building a High-Performance Dev Portfolio with React and Tailwind

Let’s build a lightning-fast, accessible developer portfolio that highlights your projects without bloated UI libraries or sluggish animations.

What you'll learn:

  • Structuring a modular portfolio project list in TypeScript
  • Building a responsive grid layout with Tailwind CSS
  • Optimizing images and loading states for a perfect 100 Lighthouse score
  • Implementing keyboard navigation and ARIA attributes for accessibility

Defining Your Project Data Structure

Hardcoding HTML for every portfolio item gets messy fast. Start by defining a strict TypeScript interface for your projects so your data stays clean and predictable.

TSX
export interface Project {
  id: string;
  title: string;
  description: string;
  techStack: string[];
  liveUrl: string;
  githubUrl: string;
  featured: boolean;
}

export const projects: Project[] = [
  {
    id: '1',
    title: 'Real-Time Analytics Dashboard',
    description: 'Tracks system metrics with WebSockets and Tailwind charts.',
    techStack: ['React', 'TypeScript', 'Tailwind', 'WebSockets'],
    liveUrl: 'https://example.com',
    githubUrl: 'https://github.com/example/dashboard',
    featured: true,
  },
  {
    id: '2',
    title: 'CLI Markdown Parser',
    description: 'A zero-dependency parser built for speed in Node.js.',
    techStack: ['TypeScript', 'Node.js', 'Jest'],
    liveUrl: 'https://example.com',
    githubUrl: 'https://github.com/example/parser',
    featured: false,
  },
];

Speed Note: Keep your project array in a separate data file or fetch it statically at build time. Avoid client-side fetching for static content like a portfolio to keep Time to First Byte (TTFB) minimal.

Creating the Responsive Grid Layout

A great portfolio adapts smoothly from mobile screens to ultra-wide monitors. Use CSS Grid with auto-fit columns to let Tailwind handle the layout math automatically.

TSX
import React from 'react';
import { projects, Project } from './data';

export function ProjectGrid() {
  return (
    <section className="max-w-6xl mx-auto px-4 py-12">
      <h2 className="text-2xl font-bold tracking-tight text-slate-100 mb-8">
        Featured Work
      </h2>
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {projects.map((project) => (
          <ProjectCard key={project.id} project={project} />
        ))}
      </div>
    </section>
  );
}

Gotcha: Avoid fixed pixel widths for grid containers. Using Tailwind's grid-cols-1 md:grid-cols-2 ensures cards wrap naturally without horizontal scrollbars on smaller viewports.

Crafting the Project Card Component

Each card needs clear visual hierarchy, distinct link targets for live demos versus source code, and clean typography that remains readable in both dark and light modes.

TSX
export function ProjectCard({ project }: { project: Project }) {
  return (
    <article className="flex flex-col justify-between bg-slate-900 border border-slate-800 rounded-lg p-6 hover:border-slate-700 transition-colors">
      <div>
        <h3 className="text-lg font-semibold text-slate-100 mb-2">
          {project.title}
        </h3>
        <p className="text-slate-400 text-sm mb-4 leading-relaxed">
          {project.description}
        </p>
        <ul className="flex flex-wrap gap-2 mb-6" aria-label="Technologies used">
          {project.techStack.map((tech) => (
            <li key={tech} className="bg-slate-800 text-slate-300 text-xs px-2.5 py-1 rounded-md">
              {tech}
            </li>
          ))}
        </ul>
      </div>
      <div className="flex items-center gap-4 pt-4 border-t border-slate-800/60">
        <a 
          href={project.liveUrl} 
          target="_blank" 
          rel="noopener noreferrer"
          className="text-sm font-medium text-sky-400 hover:text-sky-300 transition-colors"
        >
          Live Demo &rarr;
        </a>
        <a 
          href={project.githubUrl} 
          target="_blank" 
          rel="noopener noreferrer"
          className="text-sm font-medium text-slate-400 hover:text-slate-200 transition-colors"
        >
          Source Code
        </a>
      </div>
    </article>
  );
}

Accessibility Tip: Always include rel="noopener noreferrer" when using target="_blank" to protect external links against tab-nabbing vulnerabilities.

Adding Keyboard Navigation and Focus States

Recruiters and fellow engineers often navigate portfolios using keyboards. Ensure interactive elements feature explicit focus rings so users always know their current position.

TSX
export function PortfolioButton({ href, children }: { href: string; children: React.ReactNode }) {
  return (
    <a
      href={href}
      className="inline-flex items-center justify-center px-4 py-2 rounded-md font-medium text-white bg-sky-600 hover:bg-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-400 focus:ring-offset-2 focus:ring-offset-slate-950 transition-all"
    >
      {children}
    </a>
  );
}

Gotcha: Never remove default focus outlines using outline-none unless you provide a high-contrast custom focus ring like the focus:ring-2 utility shown above.

Summary

Building a custom developer portfolio gives you complete control over performance, accessibility, and design without wrestling with heavy template engines. By pairing TypeScript interfaces with Tailwind CSS grids, you get a maintainable codebase that loads instantly.

Next Steps:

  • Add a dark/light theme toggle using Tailwind's class dark mode strategy.
  • Integrate MDX to write detailed case studies for each featured project.
  • Run a Lighthouse audit to verify your performance and accessibility scores.