SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/portfolio-layouts

7 CSS Portfolio Layouts

6 Pure CSS1 with JSPublished 2026-10-05Free · no sign-up

Seven portfolio building blocks: a grid of projects with hover titles, a case study header with project facts, a minimal numbered index, a split intro with bio and links, a filterable grid, a featured project above smaller ones and a contact section that shows the email address.

01. Project Grid with Hover Titles

PortfolioGrid

A grid of project tiles whose titles and categories appear on hover and focus, over a darkening overlay. Each tile is a link with the project name as visible text, so the grid works fully without the hover.

View code
<ul class="po1"><li><a href="#p1" style="--g:linear-gradient(135deg,#f97316,#9a3412)"><b>Kiln &amp; Co</b><span>Branding</span></a></li><li><a href="#p2" style="--g:linear-gradient(135deg,#06b6d4,#1e3a8a)"><b>Tidal</b><span>App design</span></a></li><li><a href="#p3" style="--g:linear-gradient(135deg,#84cc16,#166534)"><b>Field Notes</b><span>Editorial</span></a></li><li><a href="#p4" style="--g:linear-gradient(135deg,#ec4899,#701a75)"><b>Bloom</b><span>Ecommerce</span></a></li></ul>
.po1 { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; width: 420px; max-width: 100%; margin: 0; padding: 0; list-style: none; font-family: Inter, system-ui, sans-serif; }
.po1 a { position: relative; display: grid; align-content: end; aspect-ratio: 1; padding: 12px; border-radius: 12px; background: var(--g); color: #fff; text-decoration: none; overflow: hidden; }
.po1 a::before { content: ""; position: absolute; inset: 0; background: linear-gradient(transparent 30%, rgba(0,0,0,.75)); opacity: .6; transition: opacity .3s; }
.po1 b, .po1 span { position: relative; transition: transform .3s; }
.po1 span { color: #e2e8f0; font-size: 12px; }
.po1 a:hover::before, .po1 a:focus-visible::before { opacity: 1; }
.po1 a:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }

02. Case Study Header

PortfolioCase study

The top of a case study page: client and project title, a short summary, and the project facts (role, year, services) as a description list, above a hero image. Facts as a dl keep each label paired with its value.

View code
<header class="po2"><p class="k">Case study · Linea Home</p><h3>Doubling repeat orders with a calmer checkout</h3><dl><div><dt>Role</dt><dd>Design and build</dd></div><div><dt>Year</dt><dd>2026</dd></div><div><dt>Services</dt><dd>UX, Shopify, CRO</dd></div></dl><div class="hero" aria-hidden="true"></div></header>
.po2 { width: 520px; max-width: 100%; font-family: Inter, system-ui, sans-serif; color: #101828; }
.po2 .k { margin: 0; color: #6941c6; font-size: 13px; font-weight: 700; }
.po2 h3 { margin: 6px 0 14px; font-size: 26px; line-height: 1.2; text-wrap: balance; }
.po2 dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 10px; margin: 0 0 16px; padding: 12px 0; border-top: 1px solid #e4e7ec; border-bottom: 1px solid #e4e7ec; }
.po2 dt { color: #475467; font-size: 12px; } .po2 dd { margin: 2px 0 0; font-weight: 600; font-size: 14px; }
.po2 .hero { aspect-ratio: 16 / 8; border-radius: 16px; background: linear-gradient(135deg, #f4ebff, #9e77ed); }

03. Numbered Project Index

PortfolioList

A text-only index of projects, each row a link with a number, name, discipline and year, a confident minimal portfolio that loads instantly. The numbers come from a CSS counter on an ordered list, and rows highlight on hover and focus.

View code
<ol class="po3"><li><a href="#p1"><b>Kiln &amp; Co</b><span>Identity</span><time>2026</time></a></li><li><a href="#p2"><b>Tidal</b><span>Product</span><time>2025</time></a></li><li><a href="#p3"><b>Field Notes</b><span>Editorial</span><time>2025</time></a></li><li><a href="#p4"><b>Bloom</b><span>Ecommerce</span><time>2024</time></a></li></ol>
.po3 { width: 460px; max-width: 100%; margin: 0; padding: 0; list-style: none; counter-reset: p; font-family: Inter, system-ui, sans-serif; }
.po3 li { counter-increment: p; border-top: 1px solid #334155; } .po3 li:last-child { border-bottom: 1px solid #334155; }
.po3 a { display: grid; grid-template-columns: 34px 1fr auto auto; align-items: baseline; gap: 12px; padding: 14px 6px; color: #e2e8f0; text-decoration: none; transition: background .2s, padding .2s; }
.po3 a::before { content: counter(p, decimal-leading-zero); color: #64748b; font: 500 12px "JetBrains Mono", ui-monospace, monospace; }
.po3 b { font-size: 18px; } .po3 span { color: #94a3b8; font-size: 13px; } .po3 time { color: #64748b; font: 500 12px "JetBrains Mono", ui-monospace, monospace; }
.po3 a:hover, .po3 a:focus-visible { background: #1e293b; padding-left: 12px; }
.po3 a:focus-visible { outline: 2px solid #a5b4fc; outline-offset: -2px; }
@media (max-width: 380px) { .po3 a { grid-template-columns: 30px 1fr auto; } .po3 span { display: none; } }

04. Split Intro: Bio and Links

PortfolioAbout

A personal portfolio opener: a big statement and short bio on one side, contact and social links on the other. The two parts sit side by side when the container is wide enough and stack otherwise, using a container query.

View code
<section class="po4"><div class="in"><div><h3>I design calm interfaces for busy people.</h3><p>Product designer in Lisbon, ten years in fintech and health.</p></div><ul><li><a href="#email">Email</a></li><li><a href="#read-cv">Read.cv</a></li><li><a href="#dribbble">Dribbble</a></li></ul></div></section>
.po4 { width: 560px; max-width: 100%; container-type: inline-size; font-family: Inter, system-ui, sans-serif; }
.po4 .in { display: grid; gap: 16px; }
.po4 h3 { margin: 0 0 8px; color: #101828; font-size: 26px; line-height: 1.2; }
.po4 p { margin: 0; color: #475467; font-size: 14px; }
.po4 ul { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; align-content: start; }
.po4 a { color: #101828; font-weight: 600; text-decoration: underline; text-decoration-color: #d0d5dd; text-underline-offset: 4px; }
.po4 a:hover { text-decoration-color: #101828; }
.po4 a:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; }
@container (min-width: 460px) { .po4 .in { grid-template-columns: 1fr auto; gap: 40px; } }

05. Filterable Work Grid

PortfolioFilter

Category buttons filter the project grid, with the active category marked by aria-pressed and a live count of how many projects are showing. Hidden projects use the hidden attribute so they leave the tab order too.

View code
<div class="po5"><div class="f" role="group" aria-label="Filter projects"><button aria-pressed="true" data-c="all">All</button><button aria-pressed="false" data-c="web">Web</button><button aria-pressed="false" data-c="brand">Brand</button></div><p class="n" aria-live="polite">Showing 4 projects</p><ul><li data-c="web"><a href="#w1">Tidal app</a></li><li data-c="brand"><a href="#b1">Kiln identity</a></li><li data-c="web"><a href="#w2">Bloom shop</a></li><li data-c="brand"><a href="#b2">Fern packaging</a></li></ul></div>
.po5 { width: 400px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.po5 .f { display: flex; gap: 6px; }
.po5 .f button { padding: 7px 12px; border: 1px solid #334155; border-radius: 999px; background: transparent; color: #cbd5e1; font: 600 13px Inter, system-ui, sans-serif; cursor: pointer; }
.po5 .f [aria-pressed=true] { background: #f8fafc; color: #0f172a; }
.po5 .n { margin: 8px 0; color: #94a3b8; font-size: 12px; }
.po5 ul { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 0; padding: 0; list-style: none; }
.po5 a { display: grid; align-items: end; aspect-ratio: 4 / 3; padding: 10px; border-radius: 12px; background: #1e293b; color: #f8fafc; font-weight: 700; font-size: 14px; text-decoration: none; }
.po5 [data-c=brand] a { background: #3b0764; }
.po5 :focus-visible { outline: 2px solid #a5b4fc; outline-offset: 2px; }
var bs = document.querySelectorAll('.po5 .f button'), lis = document.querySelectorAll('.po5 li'), n = document.querySelector('.po5 .n');
bs.forEach(function (b) { b.onclick = function () { var c = b.dataset.c, k = 0; bs.forEach(function (x) { x.setAttribute('aria-pressed', x === b); }); lis.forEach(function (li) { li.hidden = c !== 'all' && li.dataset.c !== c; if (!li.hidden) k++; }); n.textContent = 'Showing ' + k + (k === 1 ? ' project' : ' projects'); }; });

07. Portfolio Contact Footer

PortfolioContact

A closing section with a big invitation, an email address as a large link and availability in plain words. The email link uses mailto and shows the address itself, which people trust more than a vague Contact me button.

View code
<section class="po7" aria-labelledby="po7h"><p class="k">Taking on projects from November</p><h3 id="po7h">Have something in mind?</h3><a href="mailto:hello@example.com">hello@example.com</a><ul><li><a href="#linkedin">LinkedIn</a></li><li><a href="#github">GitHub</a></li></ul></section>
.po7 { width: 480px; max-width: 100%; box-sizing: border-box; padding: 24px; border-radius: 18px; background: #0f172a; font-family: Inter, system-ui, sans-serif; }
.po7 .k { display: inline-flex; align-items: center; gap: 8px; margin: 0 0 10px; color: #86efac; font-size: 13px; font-weight: 600; }
.po7 .k::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }
.po7 h3 { margin: 0 0 6px; color: #94a3b8; font-size: 18px; font-weight: 600; }
.po7 > a { color: #f8fafc; font-size: clamp(22px, 7vw, 34px); font-weight: 800; text-decoration-color: #475569; text-underline-offset: 6px; overflow-wrap: anywhere; }
.po7 > a:hover { text-decoration-color: #f8fafc; }
.po7 ul { display: flex; gap: 16px; margin: 16px 0 0; padding: 0; list-style: none; }
.po7 ul a { color: #cbd5e1; font-size: 14px; }
.po7 a:focus-visible { outline: 2px solid #a5b4fc; outline-offset: 3px; }

About these CSS Portfolio Layouts

Show the work, then explain it

Visitors skim the projects first. Make every tile a link with the project name in text, and save the explanation for the case study page, starting with a clear summary of the result.

Facts as data

Role, year and services are label and value pairs. A <dl> keeps them together for screen readers and is easy to lay out as a row of facts.

Filters that report back

Filter buttons should show which filter is on with aria-pressed and say how many projects are showing in a polite live region. Hidden projects need the hidden attribute so they leave the tab order.

Make contact obvious

End with a direct way to get in touch. A visible email address is clearer and more trusted than a generic Contact me button.

Frequently asked questions

Are these projects real?

No. The names are placeholders for your own work.

Should a portfolio use a lot of animation?

A little helps; heavy animation slows down people reviewing many portfolios. Let the work be the visual.

What about SEO for a portfolio?

Give each project its own page with a descriptive title and real text about the problem and result.