Portfolio Grid

Grid showcasing previous work.

Selected work

Brand refresh
App redesign
Packaging

html

<section class="pfolio">
  <h2 class="pfolio__heading">Selected work</h2>
  <div class="pfolio__grid pfolio__grid--3 pfolio__grid--hover">
    <figure class="pfolio__item"><div style="background:#D9C8F0;" role="img" aria-label="Brand refresh for Northwind"></div><figcaption>Brand refresh</figcaption></figure>
    <figure class="pfolio__item"><div style="background:#D9C8F0;" role="img" aria-label="App redesign for Halcyon"></div><figcaption>App redesign</figcaption></figure>
    <figure class="pfolio__item"><div style="background:#D9C8F0;" role="img" aria-label="Packaging for Fenwick"></div><figcaption>Packaging</figcaption></figure>
  </div>
</section>
<style>
.pfolio { font: 15px system-ui, sans-serif; color: #241430; padding: 24px; }
.pfolio__heading { margin: 0 0 18px; font-size: 22px; font-weight: 700; }
.pfolio__grid { display: grid; gap: 16px; }
.pfolio__grid--2 { grid-template-columns: repeat(2, 1fr); }
.pfolio__grid--3 { grid-template-columns: repeat(3, 1fr); }
.pfolio__grid--4 { grid-template-columns: repeat(4, 1fr); }
.pfolio__item { margin: 0; position: relative; border-radius: 10px; overflow: hidden; }
.pfolio__item div { aspect-ratio: 4 / 3; opacity: .85; }
.pfolio__item figcaption { position: absolute; left: 0; right: 0; bottom: 0; padding: 8px 10px; background: #241430cc; color: #FAF7F2; font-size: 13px; font-weight: 600; opacity: 0; }
.pfolio__grid--always figcaption { opacity: 1; }
.pfolio__grid--hover .pfolio__item:hover figcaption, .pfolio__grid--hover .pfolio__item:focus-within figcaption { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .pfolio__item figcaption { transition: opacity .15s; }
}
</style>

tailwind

<section class="font-sans text-[#241430] p-6">
  <h2 class="m-0 mb-4.5 text-2xl font-bold">Selected work</h2>
  <div class="pfolio-tw pfolio-tw--3 pfolio-tw--hover grid gap-4">
    <figure class="pfolio-tw__item m-0 relative rounded-[10px] overflow-hidden"><div class="aspect-[4/3] opacity-85" style="background:#D9C8F0;" role="img" aria-label="Brand refresh for Northwind"></div><figcaption class="pfolio-tw__cap absolute left-0 right-0 bottom-0 px-2.5 py-2 bg-[#241430cc] text-[#FAF7F2] text-[13px] font-semibold">Brand refresh</figcaption></figure>
    <figure class="pfolio-tw__item m-0 relative rounded-[10px] overflow-hidden"><div class="aspect-[4/3] opacity-85" style="background:#D9C8F0;" role="img" aria-label="App redesign for Halcyon"></div><figcaption class="pfolio-tw__cap absolute left-0 right-0 bottom-0 px-2.5 py-2 bg-[#241430cc] text-[#FAF7F2] text-[13px] font-semibold">App redesign</figcaption></figure>
    <figure class="pfolio-tw__item m-0 relative rounded-[10px] overflow-hidden"><div class="aspect-[4/3] opacity-85" style="background:#D9C8F0;" role="img" aria-label="Packaging for Fenwick"></div><figcaption class="pfolio-tw__cap absolute left-0 right-0 bottom-0 px-2.5 py-2 bg-[#241430cc] text-[#FAF7F2] text-[13px] font-semibold">Packaging</figcaption></figure>
  </div>
</section>
<style>
.pfolio-tw--2 { grid-template-columns: repeat(2, 1fr); }
.pfolio-tw--3 { grid-template-columns: repeat(3, 1fr); }
.pfolio-tw--4 { grid-template-columns: repeat(4, 1fr); }
.pfolio-tw__cap { opacity: 0; }
.pfolio-tw--always .pfolio-tw__cap { opacity: 1; }
.pfolio-tw--hover .pfolio-tw__item:hover .pfolio-tw__cap, .pfolio-tw--hover .pfolio-tw__item:focus-within .pfolio-tw__cap { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .pfolio-tw__cap { transition: opacity .15s; }
}
</style>

react

export function PortfolioGrid({
  heading = "Selected work",
  columns = "3",
  alwaysShowCaptions = false,
  accent = "#D9C8F0",
}) {
  const items = [
    { label: "Brand refresh", alt: "Brand refresh for Northwind" },
    { label: "App redesign", alt: "App redesign for Halcyon" },
    { label: "Packaging", alt: "Packaging for Fenwick" },
  ];
  return (
    <section style={{ font: "15px system-ui, sans-serif", color: "#241430", padding: 24 }}>
      <h2 style={{ margin: "0 0 18px", fontSize: 22, fontWeight: 700 }}>{heading}</h2>
      <div style={{ display: "grid", gap: 16, gridTemplateColumns: `repeat(${columns}, 1fr)` }}>
        {items.map((item) => (
          <figure key={item.label} style={{ margin: 0, position: "relative", borderRadius: 10, overflow: "hidden" }}>
            <div role="img" aria-label={item.alt} style={{ aspectRatio: "4 / 3", opacity: 0.85, background: accent }} />
            <figcaption style={{ position: "absolute", left: 0, right: 0, bottom: 0, padding: "8px 10px",
              background: "#241430cc", color: "#FAF7F2", fontSize: 13, fontWeight: 600,
              opacity: alwaysShowCaptions ? 1 : 0 }}>{item.label}</figcaption>
          </figure>
        ))}
      </div>
    </section>
  );
}