16 CSS Portfolio Layouts02 / 16
Bento Grid Portfolio Layout HTML CSS
The Apple-keynote bento: a 6-column grid where every tile buys the exact real estate its content deserves — a 4×2 statement tile, a 2×2 profile, single-row stat chips, twin project covers — all placed with nothing but grid-column/row spans. A sheen sweeps across tiles on hover, the availability dot pulses, and a container query re-deals the whole board into two columns when space runs out.
Published
The code
<section class="cpl-02" aria-label="Bento grid portfolio layout demo">
<div class="cpl-02__stage">
<div class="cpl-02__board">
<article class="cpl-02__tile cpl-02__t-hero cpl-02__dark">
<p class="cpl-02__kicker">Jules Aldana — Product Designer</p>
<h2>Designing calm interfaces for loud problems.</h2>
<a class="cpl-02__cta" href="#contact">Start a project<span aria-hidden="true">→</span></a>
</article>
<article class="cpl-02__tile cpl-02__t-face" aria-label="Portrait">
<span class="cpl-02__face" role="img" aria-label="Portrait of Jules Aldana" style="--img:url('https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=900&auto=format&fit=crop')"></span>
</article>
<article class="cpl-02__tile cpl-02__t-stat"><strong>48</strong><span>projects shipped</span></article>
<article class="cpl-02__tile cpl-02__t-stat"><strong>9<i>yrs</i></strong><span>in product teams</span></article>
<article class="cpl-02__tile cpl-02__t-stat cpl-02__avail"><strong><em aria-hidden="true"></em>Open</strong><span>for Q4 2026</span></article>
<a class="cpl-02__tile cpl-02__t-work" href="#atlas" style="--img:url('https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=900&auto=format&fit=crop');--a:oklch(0.75 0.13 300);--b:oklch(0.45 0.17 270)">
<span class="cpl-02__chip">Fintech</span><h3>Atlas Wealth App</h3>
</a>
<a class="cpl-02__tile cpl-02__t-work" href="#ferra" style="--img:url('https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=900&auto=format&fit=crop');--a:oklch(0.83 0.11 60);--b:oklch(0.55 0.16 30)">
<span class="cpl-02__chip">E-commerce</span><h3>Ferra Storefront</h3>
</a>
<article class="cpl-02__tile cpl-02__t-strip">
<span>Trusted by</span>
<b>Linear</b><b>Vercel</b><b>Arc</b><b>Raycast</b>
</article>
</div>
</div>
</section><section class="cpl-02" aria-label="Bento grid portfolio layout demo">
<div class="cpl-02__stage">
<div class="cpl-02__board">
<article class="cpl-02__tile cpl-02__t-hero cpl-02__dark">
<p class="cpl-02__kicker">Jules Aldana — Product Designer</p>
<h2>Designing calm interfaces for loud problems.</h2>
<a class="cpl-02__cta" href="#contact">Start a project<span aria-hidden="true">→</span></a>
</article>
<article class="cpl-02__tile cpl-02__t-face" aria-label="Portrait">
<span class="cpl-02__face" role="img" aria-label="Portrait of Jules Aldana" style="--img:url('https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=900&auto=format&fit=crop')"></span>
</article>
<article class="cpl-02__tile cpl-02__t-stat"><strong>48</strong><span>projects shipped</span></article>
<article class="cpl-02__tile cpl-02__t-stat"><strong>9<i>yrs</i></strong><span>in product teams</span></article>
<article class="cpl-02__tile cpl-02__t-stat cpl-02__avail"><strong><em aria-hidden="true"></em>Open</strong><span>for Q4 2026</span></article>
<a class="cpl-02__tile cpl-02__t-work" href="#atlas" style="--img:url('https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=900&auto=format&fit=crop');--a:oklch(0.75 0.13 300);--b:oklch(0.45 0.17 270)">
<span class="cpl-02__chip">Fintech</span><h3>Atlas Wealth App</h3>
</a>
<a class="cpl-02__tile cpl-02__t-work" href="#ferra" style="--img:url('https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=900&auto=format&fit=crop');--a:oklch(0.83 0.11 60);--b:oklch(0.55 0.16 30)">
<span class="cpl-02__chip">E-commerce</span><h3>Ferra Storefront</h3>
</a>
<article class="cpl-02__tile cpl-02__t-strip">
<span>Trusted by</span>
<b>Linear</b><b>Vercel</b><b>Arc</b><b>Raycast</b>
</article>
</div>
</div>
</section>.cpl-02,
.cpl-02 *,
.cpl-02 *::before,
.cpl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cpl-02 {
--bg: oklch(0.96 0.005 90);
--card: oklch(1 0 0);
--ink: oklch(0.23 0.02 270);
--mut: oklch(0.54 0.015 270);
--line: oklch(0.89 0.008 270);
--dark: oklch(0.2 0.03 280);
--acc: oklch(0.72 0.17 55);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.cpl-02__stage {
width: 100%;
container: cpl02/inline-size;
background: var(--bg);
padding: clamp(14px,3cqi,22px);
min-height: 100vh;
min-height: 100svh;
display: flex;
flex-direction: column;
justify-content: center;
}
.cpl-02__board {
display: grid;
gap: 12px;
grid-template-columns: repeat(6,1fr);
grid-auto-rows: clamp(92px,11vh,132px);
}
.cpl-02__tile {
position: relative;
overflow: hidden;
border-radius: 18px;
background: var(--card);
border: 1px solid var(--line);
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
gap: 6px;
text-decoration: none;
color: inherit;
transition: translate .25s cubic-bezier(.2,.7,.2,1),box-shadow .25s;
}
.cpl-02__tile::after {
content: "";
position: absolute;
inset: -40% auto;
left: 0;
width: 45%;
background: linear-gradient(105deg,transparent,oklch(1 0 0/.14),transparent);
transform: translateX(-150%) rotate(8deg);
transition: transform .7s cubic-bezier(.4,0,.2,1);
pointer-events: none;
}
.cpl-02__tile:hover {
translate: 0 -3px;
box-shadow: 0 16px 30px -18px oklch(0.3 0.04 280/.5);
}
.cpl-02__tile:hover::after {
transform: translateX(280%) rotate(8deg);
}
a.cpl-02__tile:focus-visible {
outline: 2px solid var(--acc);
outline-offset: 3px;
}
.cpl-02__t-hero {
grid-column: span 4;
grid-row: span 2;
justify-content: space-between;
padding: 24px;
}
.cpl-02__dark {
background: radial-gradient(120% 130% at 15% 0%,oklch(0.3 0.06 290/.9),transparent 55%),var(--dark);
border-color: oklch(0.32 0.04 285);
color: oklch(0.96 0.008 90);
}
.cpl-02__kicker {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .14em;
text-transform: uppercase;
color: oklch(0.75 0.1 55);
}
.cpl-02__t-hero h2 {
font-size: clamp(20px,3.4cqi,30px);
line-height: 1.12;
letter-spacing: -.025em;
font-weight: 700;
max-width: 14em;
text-wrap: balance;
}
.cpl-02__cta {
display: inline-flex;
align-items: center;
gap: 8px;
align-self: start;
min-height: 40px;
padding: 0 16px;
border-radius: 99px;
font-size: 13px;
font-weight: 700;
text-decoration: none;
color: var(--dark);
background: var(--acc);
transition: gap .25s,background .25s;
}
.cpl-02__cta:hover,
.cpl-02__cta:focus-visible {
gap: 13px;
background: oklch(0.78 0.17 55);
}
.cpl-02__cta:focus-visible {
outline: 2px solid oklch(0.96 0.008 90);
outline-offset: 2px;
}
.cpl-02__t-face {
grid-column: span 2;
grid-row: span 2;
padding: 0;
}
.cpl-02__face {
position: absolute;
inset: 0;
background-image: var(--img,none),linear-gradient(160deg,oklch(0.88 0.04 90),oklch(0.7 0.09 60));
background-size: cover;
background-position: center;
}
.cpl-02__t-stat {
grid-column: span 2;
}
.cpl-02__t-stat strong {
font-size: 26px;
letter-spacing: -.02em;
font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 6px;
}
.cpl-02__t-stat strong i {
font-size: 13px;
font-style: normal;
color: var(--mut);
}
.cpl-02__t-stat span {
font-size: 12px;
color: var(--mut);
}
.cpl-02__avail strong {
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.cpl-02__avail em {
width: 9px;
height: 9px;
border-radius: 50%;
background: oklch(0.72 0.19 150);
animation: cpl02-pulse 2s infinite;
}
@keyframes cpl02-pulse {
0% {
box-shadow: 0 0 0 0 oklch(0.72 0.19 150/.5);
}
70% {
box-shadow: 0 0 0 9px oklch(0.72 0.19 150/0);
}
100% {
box-shadow: 0 0 0 0 oklch(0.72 0.19 150/0);
}
}
.cpl-02__t-work {
grid-column: span 3;
grid-row: span 2;
justify-content: flex-end;
background-image: var(--img,none),linear-gradient(150deg,var(--a),var(--b));
background-size: cover;
background-position: center;
border: none;
color: oklch(0.98 0.005 90);
}
.cpl-02__t-work h3 {
font-size: 19px;
letter-spacing: -.015em;
}
.cpl-02__chip {
align-self: start;
padding: 4px 10px;
border-radius: 99px;
font-size: 11px;
font-weight: 700;
background: oklch(1 0 0/.2);
backdrop-filter: blur(4px);
margin-bottom: auto;
}
.cpl-02__t-strip {
grid-column: span 6;
flex-direction: row;
align-items: center;
gap: 22px;
padding: 0 24px;
}
.cpl-02__t-strip span {
font-size: 11.5px;
font-weight: 600;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--mut);
}
.cpl-02__t-strip b {
font-size: 14.5px;
letter-spacing: -.01em;
opacity: .75;
}
@container cpl02 (width < 560px) {
.cpl-02__board {
grid-template-columns: repeat(2,1fr);
grid-auto-rows: 88px;
}
.cpl-02__t-hero {
grid-column: span 2;
grid-row: span 2;
}
.cpl-02__t-face {
grid-column: span 1;
grid-row: span 1;
}
.cpl-02__t-stat {
grid-column: span 1;
}
.cpl-02__t-work {
grid-column: span 2;
}
.cpl-02__t-strip {
grid-column: span 2;
flex-wrap: wrap;
gap: 12px;
padding: 14px 18px;
}
}
.cpl-02__t-work::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top,oklch(0.2 0.03 280/.72),transparent 55%);
}
.cpl-02__t-work .cpl-02__chip,
.cpl-02__t-work h3 {
position: relative;
z-index: 1;
}
@media (prefers-reduced-motion: reduce) {
.cpl-02 *,
.cpl-02 *::after {
transition-duration: .01ms !important;
animation: none !important;
}
}.cpl-02,
.cpl-02 *,
.cpl-02 *::before,
.cpl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cpl-02 {
--bg: oklch(0.96 0.005 90);
--card: oklch(1 0 0);
--ink: oklch(0.23 0.02 270);
--mut: oklch(0.54 0.015 270);
--line: oklch(0.89 0.008 270);
--dark: oklch(0.2 0.03 280);
--acc: oklch(0.72 0.17 55);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.cpl-02__stage {
width: 100%;
container: cpl02/inline-size;
background: var(--bg);
padding: clamp(14px,3cqi,22px);
min-height: 100vh;
min-height: 100svh;
display: flex;
flex-direction: column;
justify-content: center;
}
.cpl-02__board {
display: grid;
gap: 12px;
grid-template-columns: repeat(6,1fr);
grid-auto-rows: clamp(92px,11vh,132px);
}
.cpl-02__tile {
position: relative;
overflow: hidden;
border-radius: 18px;
background: var(--card);
border: 1px solid var(--line);
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
gap: 6px;
text-decoration: none;
color: inherit;
transition: translate .25s cubic-bezier(.2,.7,.2,1),box-shadow .25s;
}
.cpl-02__tile::after {
content: "";
position: absolute;
inset: -40% auto;
left: 0;
width: 45%;
background: linear-gradient(105deg,transparent,oklch(1 0 0/.14),transparent);
transform: translateX(-150%) rotate(8deg);
transition: transform .7s cubic-bezier(.4,0,.2,1);
pointer-events: none;
}
.cpl-02__tile:hover {
translate: 0 -3px;
box-shadow: 0 16px 30px -18px oklch(0.3 0.04 280/.5);
}
.cpl-02__tile:hover::after {
transform: translateX(280%) rotate(8deg);
}
a.cpl-02__tile:focus-visible {
outline: 2px solid var(--acc);
outline-offset: 3px;
}
.cpl-02__t-hero {
grid-column: span 4;
grid-row: span 2;
justify-content: space-between;
padding: 24px;
}
.cpl-02__dark {
background: radial-gradient(120% 130% at 15% 0%,oklch(0.3 0.06 290/.9),transparent 55%),var(--dark);
border-color: oklch(0.32 0.04 285);
color: oklch(0.96 0.008 90);
}
.cpl-02__kicker {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .14em;
text-transform: uppercase;
color: oklch(0.75 0.1 55);
}
.cpl-02__t-hero h2 {
font-size: clamp(20px,3.4cqi,30px);
line-height: 1.12;
letter-spacing: -.025em;
font-weight: 700;
max-width: 14em;
text-wrap: balance;
}
.cpl-02__cta {
display: inline-flex;
align-items: center;
gap: 8px;
align-self: start;
min-height: 40px;
padding: 0 16px;
border-radius: 99px;
font-size: 13px;
font-weight: 700;
text-decoration: none;
color: var(--dark);
background: var(--acc);
transition: gap .25s,background .25s;
}
.cpl-02__cta:hover,
.cpl-02__cta:focus-visible {
gap: 13px;
background: oklch(0.78 0.17 55);
}
.cpl-02__cta:focus-visible {
outline: 2px solid oklch(0.96 0.008 90);
outline-offset: 2px;
}
.cpl-02__t-face {
grid-column: span 2;
grid-row: span 2;
padding: 0;
}
.cpl-02__face {
position: absolute;
inset: 0;
background-image: var(--img,none),linear-gradient(160deg,oklch(0.88 0.04 90),oklch(0.7 0.09 60));
background-size: cover;
background-position: center;
}
.cpl-02__t-stat {
grid-column: span 2;
}
.cpl-02__t-stat strong {
font-size: 26px;
letter-spacing: -.02em;
font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 6px;
}
.cpl-02__t-stat strong i {
font-size: 13px;
font-style: normal;
color: var(--mut);
}
.cpl-02__t-stat span {
font-size: 12px;
color: var(--mut);
}
.cpl-02__avail strong {
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.cpl-02__avail em {
width: 9px;
height: 9px;
border-radius: 50%;
background: oklch(0.72 0.19 150);
animation: cpl02-pulse 2s infinite;
}
@keyframes cpl02-pulse {
0% {
box-shadow: 0 0 0 0 oklch(0.72 0.19 150/.5);
}
70% {
box-shadow: 0 0 0 9px oklch(0.72 0.19 150/0);
}
100% {
box-shadow: 0 0 0 0 oklch(0.72 0.19 150/0);
}
}
.cpl-02__t-work {
grid-column: span 3;
grid-row: span 2;
justify-content: flex-end;
background-image: var(--img,none),linear-gradient(150deg,var(--a),var(--b));
background-size: cover;
background-position: center;
border: none;
color: oklch(0.98 0.005 90);
}
.cpl-02__t-work h3 {
font-size: 19px;
letter-spacing: -.015em;
}
.cpl-02__chip {
align-self: start;
padding: 4px 10px;
border-radius: 99px;
font-size: 11px;
font-weight: 700;
background: oklch(1 0 0/.2);
backdrop-filter: blur(4px);
margin-bottom: auto;
}
.cpl-02__t-strip {
grid-column: span 6;
flex-direction: row;
align-items: center;
gap: 22px;
padding: 0 24px;
}
.cpl-02__t-strip span {
font-size: 11.5px;
font-weight: 600;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--mut);
}
.cpl-02__t-strip b {
font-size: 14.5px;
letter-spacing: -.01em;
opacity: .75;
}
@container cpl02 (width < 560px) {
.cpl-02__board {
grid-template-columns: repeat(2,1fr);
grid-auto-rows: 88px;
}
.cpl-02__t-hero {
grid-column: span 2;
grid-row: span 2;
}
.cpl-02__t-face {
grid-column: span 1;
grid-row: span 1;
}
.cpl-02__t-stat {
grid-column: span 1;
}
.cpl-02__t-work {
grid-column: span 2;
}
.cpl-02__t-strip {
grid-column: span 2;
flex-wrap: wrap;
gap: 12px;
padding: 14px 18px;
}
}
.cpl-02__t-work::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top,oklch(0.2 0.03 280/.72),transparent 55%);
}
.cpl-02__t-work .cpl-02__chip,
.cpl-02__t-work h3 {
position: relative;
z-index: 1;
}
@media (prefers-reduced-motion: reduce) {
.cpl-02 *,
.cpl-02 *::after {
transition-duration: .01ms !important;
animation: none !important;
}
}Here's a working CSS Portfolio Layout from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Bento Grid Portfolio Layout HTML CSS
Source: https://codefronts.com/layouts/css-portfolio-layouts/bento-grid-portfolio-layout-html-css/
The Apple-keynote bento: a 6-column grid where every tile buys the exact real estate its content deserves — a 4×2 statement tile, a 2×2 profile, single-row stat chips, twin project covers — all placed with nothing but grid-column/row spans. A sheen sweeps across tiles on hover, the availability dot pulses, and a container query re-deals the whole board into two columns when space runs out.
## HTML
```html
<section class="cpl-02" aria-label="Bento grid portfolio layout demo">
<div class="cpl-02__stage">
<div class="cpl-02__board">
<article class="cpl-02__tile cpl-02__t-hero cpl-02__dark">
<p class="cpl-02__kicker">Jules Aldana — Product Designer</p>
<h2>Designing calm interfaces for loud problems.</h2>
<a class="cpl-02__cta" href="#contact">Start a project<span aria-hidden="true">→</span></a>
</article>
<article class="cpl-02__tile cpl-02__t-face" aria-label="Portrait">
<span class="cpl-02__face" role="img" aria-label="Portrait of Jules Aldana" style="--img:url('https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=900&auto=format&fit=crop')"></span>
</article>
<article class="cpl-02__tile cpl-02__t-stat"><strong>48</strong><span>projects shipped</span></article>
<article class="cpl-02__tile cpl-02__t-stat"><strong>9<i>yrs</i></strong><span>in product teams</span></article>
<article class="cpl-02__tile cpl-02__t-stat cpl-02__avail"><strong><em aria-hidden="true"></em>Open</strong><span>for Q4 2026</span></article>
<a class="cpl-02__tile cpl-02__t-work" href="#atlas" style="--img:url('https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=900&auto=format&fit=crop');--a:oklch(0.75 0.13 300);--b:oklch(0.45 0.17 270)">
<span class="cpl-02__chip">Fintech</span><h3>Atlas Wealth App</h3>
</a>
<a class="cpl-02__tile cpl-02__t-work" href="#ferra" style="--img:url('https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=900&auto=format&fit=crop');--a:oklch(0.83 0.11 60);--b:oklch(0.55 0.16 30)">
<span class="cpl-02__chip">E-commerce</span><h3>Ferra Storefront</h3>
</a>
<article class="cpl-02__tile cpl-02__t-strip">
<span>Trusted by</span>
<b>Linear</b><b>Vercel</b><b>Arc</b><b>Raycast</b>
</article>
</div>
</div>
</section>
```
## CSS
```css
.cpl-02,
.cpl-02 *,
.cpl-02 *::before,
.cpl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cpl-02 {
--bg: oklch(0.96 0.005 90);
--card: oklch(1 0 0);
--ink: oklch(0.23 0.02 270);
--mut: oklch(0.54 0.015 270);
--line: oklch(0.89 0.008 270);
--dark: oklch(0.2 0.03 280);
--acc: oklch(0.72 0.17 55);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.cpl-02__stage {
width: 100%;
container: cpl02/inline-size;
background: var(--bg);
padding: clamp(14px,3cqi,22px);
min-height: 100vh;
min-height: 100svh;
display: flex;
flex-direction: column;
justify-content: center;
}
.cpl-02__board {
display: grid;
gap: 12px;
grid-template-columns: repeat(6,1fr);
grid-auto-rows: clamp(92px,11vh,132px);
}
.cpl-02__tile {
position: relative;
overflow: hidden;
border-radius: 18px;
background: var(--card);
border: 1px solid var(--line);
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
gap: 6px;
text-decoration: none;
color: inherit;
transition: translate .25s cubic-bezier(.2,.7,.2,1),box-shadow .25s;
}
.cpl-02__tile::after {
content: "";
position: absolute;
inset: -40% auto;
left: 0;
width: 45%;
background: linear-gradient(105deg,transparent,oklch(1 0 0/.14),transparent);
transform: translateX(-150%) rotate(8deg);
transition: transform .7s cubic-bezier(.4,0,.2,1);
pointer-events: none;
}
.cpl-02__tile:hover {
translate: 0 -3px;
box-shadow: 0 16px 30px -18px oklch(0.3 0.04 280/.5);
}
.cpl-02__tile:hover::after {
transform: translateX(280%) rotate(8deg);
}
a.cpl-02__tile:focus-visible {
outline: 2px solid var(--acc);
outline-offset: 3px;
}
.cpl-02__t-hero {
grid-column: span 4;
grid-row: span 2;
justify-content: space-between;
padding: 24px;
}
.cpl-02__dark {
background: radial-gradient(120% 130% at 15% 0%,oklch(0.3 0.06 290/.9),transparent 55%),var(--dark);
border-color: oklch(0.32 0.04 285);
color: oklch(0.96 0.008 90);
}
.cpl-02__kicker {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .14em;
text-transform: uppercase;
color: oklch(0.75 0.1 55);
}
.cpl-02__t-hero h2 {
font-size: clamp(20px,3.4cqi,30px);
line-height: 1.12;
letter-spacing: -.025em;
font-weight: 700;
max-width: 14em;
text-wrap: balance;
}
.cpl-02__cta {
display: inline-flex;
align-items: center;
gap: 8px;
align-self: start;
min-height: 40px;
padding: 0 16px;
border-radius: 99px;
font-size: 13px;
font-weight: 700;
text-decoration: none;
color: var(--dark);
background: var(--acc);
transition: gap .25s,background .25s;
}
.cpl-02__cta:hover,
.cpl-02__cta:focus-visible {
gap: 13px;
background: oklch(0.78 0.17 55);
}
.cpl-02__cta:focus-visible {
outline: 2px solid oklch(0.96 0.008 90);
outline-offset: 2px;
}
.cpl-02__t-face {
grid-column: span 2;
grid-row: span 2;
padding: 0;
}
.cpl-02__face {
position: absolute;
inset: 0;
background-image: var(--img,none),linear-gradient(160deg,oklch(0.88 0.04 90),oklch(0.7 0.09 60));
background-size: cover;
background-position: center;
}
.cpl-02__t-stat {
grid-column: span 2;
}
.cpl-02__t-stat strong {
font-size: 26px;
letter-spacing: -.02em;
font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 6px;
}
.cpl-02__t-stat strong i {
font-size: 13px;
font-style: normal;
color: var(--mut);
}
.cpl-02__t-stat span {
font-size: 12px;
color: var(--mut);
}
.cpl-02__avail strong {
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.cpl-02__avail em {
width: 9px;
height: 9px;
border-radius: 50%;
background: oklch(0.72 0.19 150);
animation: cpl02-pulse 2s infinite;
}
@keyframes cpl02-pulse {
0% {
box-shadow: 0 0 0 0 oklch(0.72 0.19 150/.5);
}
70% {
box-shadow: 0 0 0 9px oklch(0.72 0.19 150/0);
}
100% {
box-shadow: 0 0 0 0 oklch(0.72 0.19 150/0);
}
}
.cpl-02__t-work {
grid-column: span 3;
grid-row: span 2;
justify-content: flex-end;
background-image: var(--img,none),linear-gradient(150deg,var(--a),var(--b));
background-size: cover;
background-position: center;
border: none;
color: oklch(0.98 0.005 90);
}
.cpl-02__t-work h3 {
font-size: 19px;
letter-spacing: -.015em;
}
.cpl-02__chip {
align-self: start;
padding: 4px 10px;
border-radius: 99px;
font-size: 11px;
font-weight: 700;
background: oklch(1 0 0/.2);
backdrop-filter: blur(4px);
margin-bottom: auto;
}
.cpl-02__t-strip {
grid-column: span 6;
flex-direction: row;
align-items: center;
gap: 22px;
padding: 0 24px;
}
.cpl-02__t-strip span {
font-size: 11.5px;
font-weight: 600;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--mut);
}
.cpl-02__t-strip b {
font-size: 14.5px;
letter-spacing: -.01em;
opacity: .75;
}
@container cpl02 (width < 560px) {
.cpl-02__board {
grid-template-columns: repeat(2,1fr);
grid-auto-rows: 88px;
}
.cpl-02__t-hero {
grid-column: span 2;
grid-row: span 2;
}
.cpl-02__t-face {
grid-column: span 1;
grid-row: span 1;
}
.cpl-02__t-stat {
grid-column: span 1;
}
.cpl-02__t-work {
grid-column: span 2;
}
.cpl-02__t-strip {
grid-column: span 2;
flex-wrap: wrap;
gap: 12px;
padding: 14px 18px;
}
}
.cpl-02__t-work::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top,oklch(0.2 0.03 280/.72),transparent 55%);
}
.cpl-02__t-work .cpl-02__chip,
.cpl-02__t-work h3 {
position: relative;
z-index: 1;
}
@media (prefers-reduced-motion: reduce) {
.cpl-02 *,
.cpl-02 *::after {
transition-duration: .01ms !important;
animation: none !important;
}
}
```Here's a working CSS Portfolio Layout from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Bento Grid Portfolio Layout HTML CSS
Source: https://codefronts.com/layouts/css-portfolio-layouts/bento-grid-portfolio-layout-html-css/
The Apple-keynote bento: a 6-column grid where every tile buys the exact real estate its content deserves — a 4×2 statement tile, a 2×2 profile, single-row stat chips, twin project covers — all placed with nothing but grid-column/row spans. A sheen sweeps across tiles on hover, the availability dot pulses, and a container query re-deals the whole board into two columns when space runs out.
## HTML
```html
<section class="cpl-02" aria-label="Bento grid portfolio layout demo">
<div class="cpl-02__stage">
<div class="cpl-02__board">
<article class="cpl-02__tile cpl-02__t-hero cpl-02__dark">
<p class="cpl-02__kicker">Jules Aldana — Product Designer</p>
<h2>Designing calm interfaces for loud problems.</h2>
<a class="cpl-02__cta" href="#contact">Start a project<span aria-hidden="true">→</span></a>
</article>
<article class="cpl-02__tile cpl-02__t-face" aria-label="Portrait">
<span class="cpl-02__face" role="img" aria-label="Portrait of Jules Aldana" style="--img:url('https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=900&auto=format&fit=crop')"></span>
</article>
<article class="cpl-02__tile cpl-02__t-stat"><strong>48</strong><span>projects shipped</span></article>
<article class="cpl-02__tile cpl-02__t-stat"><strong>9<i>yrs</i></strong><span>in product teams</span></article>
<article class="cpl-02__tile cpl-02__t-stat cpl-02__avail"><strong><em aria-hidden="true"></em>Open</strong><span>for Q4 2026</span></article>
<a class="cpl-02__tile cpl-02__t-work" href="#atlas" style="--img:url('https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=900&auto=format&fit=crop');--a:oklch(0.75 0.13 300);--b:oklch(0.45 0.17 270)">
<span class="cpl-02__chip">Fintech</span><h3>Atlas Wealth App</h3>
</a>
<a class="cpl-02__tile cpl-02__t-work" href="#ferra" style="--img:url('https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=900&auto=format&fit=crop');--a:oklch(0.83 0.11 60);--b:oklch(0.55 0.16 30)">
<span class="cpl-02__chip">E-commerce</span><h3>Ferra Storefront</h3>
</a>
<article class="cpl-02__tile cpl-02__t-strip">
<span>Trusted by</span>
<b>Linear</b><b>Vercel</b><b>Arc</b><b>Raycast</b>
</article>
</div>
</div>
</section>
```
## CSS
```css
.cpl-02,
.cpl-02 *,
.cpl-02 *::before,
.cpl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cpl-02 {
--bg: oklch(0.96 0.005 90);
--card: oklch(1 0 0);
--ink: oklch(0.23 0.02 270);
--mut: oklch(0.54 0.015 270);
--line: oklch(0.89 0.008 270);
--dark: oklch(0.2 0.03 280);
--acc: oklch(0.72 0.17 55);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.cpl-02__stage {
width: 100%;
container: cpl02/inline-size;
background: var(--bg);
padding: clamp(14px,3cqi,22px);
min-height: 100vh;
min-height: 100svh;
display: flex;
flex-direction: column;
justify-content: center;
}
.cpl-02__board {
display: grid;
gap: 12px;
grid-template-columns: repeat(6,1fr);
grid-auto-rows: clamp(92px,11vh,132px);
}
.cpl-02__tile {
position: relative;
overflow: hidden;
border-radius: 18px;
background: var(--card);
border: 1px solid var(--line);
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
gap: 6px;
text-decoration: none;
color: inherit;
transition: translate .25s cubic-bezier(.2,.7,.2,1),box-shadow .25s;
}
.cpl-02__tile::after {
content: "";
position: absolute;
inset: -40% auto;
left: 0;
width: 45%;
background: linear-gradient(105deg,transparent,oklch(1 0 0/.14),transparent);
transform: translateX(-150%) rotate(8deg);
transition: transform .7s cubic-bezier(.4,0,.2,1);
pointer-events: none;
}
.cpl-02__tile:hover {
translate: 0 -3px;
box-shadow: 0 16px 30px -18px oklch(0.3 0.04 280/.5);
}
.cpl-02__tile:hover::after {
transform: translateX(280%) rotate(8deg);
}
a.cpl-02__tile:focus-visible {
outline: 2px solid var(--acc);
outline-offset: 3px;
}
.cpl-02__t-hero {
grid-column: span 4;
grid-row: span 2;
justify-content: space-between;
padding: 24px;
}
.cpl-02__dark {
background: radial-gradient(120% 130% at 15% 0%,oklch(0.3 0.06 290/.9),transparent 55%),var(--dark);
border-color: oklch(0.32 0.04 285);
color: oklch(0.96 0.008 90);
}
.cpl-02__kicker {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .14em;
text-transform: uppercase;
color: oklch(0.75 0.1 55);
}
.cpl-02__t-hero h2 {
font-size: clamp(20px,3.4cqi,30px);
line-height: 1.12;
letter-spacing: -.025em;
font-weight: 700;
max-width: 14em;
text-wrap: balance;
}
.cpl-02__cta {
display: inline-flex;
align-items: center;
gap: 8px;
align-self: start;
min-height: 40px;
padding: 0 16px;
border-radius: 99px;
font-size: 13px;
font-weight: 700;
text-decoration: none;
color: var(--dark);
background: var(--acc);
transition: gap .25s,background .25s;
}
.cpl-02__cta:hover,
.cpl-02__cta:focus-visible {
gap: 13px;
background: oklch(0.78 0.17 55);
}
.cpl-02__cta:focus-visible {
outline: 2px solid oklch(0.96 0.008 90);
outline-offset: 2px;
}
.cpl-02__t-face {
grid-column: span 2;
grid-row: span 2;
padding: 0;
}
.cpl-02__face {
position: absolute;
inset: 0;
background-image: var(--img,none),linear-gradient(160deg,oklch(0.88 0.04 90),oklch(0.7 0.09 60));
background-size: cover;
background-position: center;
}
.cpl-02__t-stat {
grid-column: span 2;
}
.cpl-02__t-stat strong {
font-size: 26px;
letter-spacing: -.02em;
font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 6px;
}
.cpl-02__t-stat strong i {
font-size: 13px;
font-style: normal;
color: var(--mut);
}
.cpl-02__t-stat span {
font-size: 12px;
color: var(--mut);
}
.cpl-02__avail strong {
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.cpl-02__avail em {
width: 9px;
height: 9px;
border-radius: 50%;
background: oklch(0.72 0.19 150);
animation: cpl02-pulse 2s infinite;
}
@keyframes cpl02-pulse {
0% {
box-shadow: 0 0 0 0 oklch(0.72 0.19 150/.5);
}
70% {
box-shadow: 0 0 0 9px oklch(0.72 0.19 150/0);
}
100% {
box-shadow: 0 0 0 0 oklch(0.72 0.19 150/0);
}
}
.cpl-02__t-work {
grid-column: span 3;
grid-row: span 2;
justify-content: flex-end;
background-image: var(--img,none),linear-gradient(150deg,var(--a),var(--b));
background-size: cover;
background-position: center;
border: none;
color: oklch(0.98 0.005 90);
}
.cpl-02__t-work h3 {
font-size: 19px;
letter-spacing: -.015em;
}
.cpl-02__chip {
align-self: start;
padding: 4px 10px;
border-radius: 99px;
font-size: 11px;
font-weight: 700;
background: oklch(1 0 0/.2);
backdrop-filter: blur(4px);
margin-bottom: auto;
}
.cpl-02__t-strip {
grid-column: span 6;
flex-direction: row;
align-items: center;
gap: 22px;
padding: 0 24px;
}
.cpl-02__t-strip span {
font-size: 11.5px;
font-weight: 600;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--mut);
}
.cpl-02__t-strip b {
font-size: 14.5px;
letter-spacing: -.01em;
opacity: .75;
}
@container cpl02 (width < 560px) {
.cpl-02__board {
grid-template-columns: repeat(2,1fr);
grid-auto-rows: 88px;
}
.cpl-02__t-hero {
grid-column: span 2;
grid-row: span 2;
}
.cpl-02__t-face {
grid-column: span 1;
grid-row: span 1;
}
.cpl-02__t-stat {
grid-column: span 1;
}
.cpl-02__t-work {
grid-column: span 2;
}
.cpl-02__t-strip {
grid-column: span 2;
flex-wrap: wrap;
gap: 12px;
padding: 14px 18px;
}
}
.cpl-02__t-work::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top,oklch(0.2 0.03 280/.72),transparent 55%);
}
.cpl-02__t-work .cpl-02__chip,
.cpl-02__t-work h3 {
position: relative;
z-index: 1;
}
@media (prefers-reduced-motion: reduce) {
.cpl-02 *,
.cpl-02 *::after {
transition-duration: .01ms !important;
animation: none !important;
}
}
```How this works
Bento is not a special technology — it is explicit spans on a fixed column count with a fixed row rhythm. Six columns and a 92px grid-auto-rows beat give every tile a shared module to snap to:
.board{ display:grid; gap:12px;
grid-template-columns: repeat(6, 1fr);
grid-auto-rows: 92px; }
.t-hero { grid-column: span 4; grid-row: span 2; }
.t-face { grid-column: span 2; grid-row: span 2; }
.t-stat { grid-column: span 2; } /* 1 row tall */
.t-work { grid-column: span 3; grid-row: span 2; }The asymmetry that makes bento feel designed comes from mixing span widths so no two adjacent tiles share both dimensions. The hover sheen is one reusable ::after: a rotated translucent gradient strip parked off-tile at translateX(-150%) that sweeps to 150% on hover — transform-only, so it never repaints the tile beneath.
At container widths under 560px the 6-column board would shrink tiles below usability, so a container query re-declares the board as repeat(2, 1fr) and remaps every span to 1 or 2 — the tiles reflow into a clean stacked bento without touching the HTML. Numbers use font-variant-numeric: tabular-nums so stats align like a dashboard, and the availability dot's pulse is a keyframed box-shadow ring that respects prefers-reduced-motion.
Make it yours
- Row rhythm:
grid-auto-rows:92pxis the module — 110px makes everything statelier; pair changes with the hero tile's font clamp. - Tile mix: add tiles freely; keep total column-spans per visual row summing to 6 for a tight pack, or let auto-placement fill holes with
grid-auto-flow:dense. - Sheen intensity: the
oklch(1 0 0/.14)stripe in.cpl-02__tile::after; drop to.07for subtle luxe, raise duration for a slower sweep. - Theme: the board mixes light and dark tiles via a
--darkclass-level swap — invert the ratio (mostly dark, one light) for the moody-studio variant by moving the class.
Gotchas — read before shipping
- Do not build bento with absolute positioning — spans keep it responsive and source-order accessible; screen readers read tiles in DOM order regardless of visual position.
- A tile with
span 4on a 2-column board silently explodes the grid — every span must be remapped inside the narrow container query, not just the template. backdrop-filteron many overlapping tiles gets expensive; this demo uses flat oklch fills plus one gradient per tile instead — sheen is the only animated layer.- Keep hover lift ≤ 3px on dense boards — big lifts cause neighbouring tiles to visually collide since gaps are only 12px.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), backdrop-filter, @container, text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
Spans and auto-rows are universal grid features; the floor is oklch()/color-mix() and container queries. Hex fallbacks push support back to any grid-capable browser.