02 / 20
All 20 in 20 CSS Claymorphism
- 01Claymorphism Shadow Recipe Reference Build
- 02Inner Highlight and Inner Shadow Layering
- 03Border Radius Scale for Puffy Shapes
- 04Claymorphism vs Neumorphism Side by Side
- 05Claymorphism with Glassmorphism Hybrid
- 06Light Source Consistency Across a Clay Layout
- 07Clay Depth Tiers for Visual Hierarchy
- 08Tailwind CSS Claymorphism Implementation
- 09Clay Button Press and Squish States
- 10Clay Toggle Switch and Checkbox Set
- 11Clay Input Field and Search Bar
- 12Clay Badge & Pill Tag System
- 13Claymorphism Card Component
- 14Clay Modal Dialog with Floating Depth
- 15Clay Pricing Table with Raised Popular Tier
- 16Clay Bottom Tab Bar for Mobile
- 17Clay Icon Tiles and App Grid
- 18Clay Toast & Floating Alert Bubbles
- 19Clay Dark Mode Shadow Recalibration
- 20Clay Animation Performance & Transform Optimization
Inner Highlight and Inner Shadow Layering
Isolate the two inset layers and the material becomes obvious: highlight alone looks inflated, shade alone looks hollow, together they look like clay you could press a thumb into. Three spheres show each state, and the hero sphere animates its light around the object using @property-registered lengths — a genuinely animatable inset shadow, not a cross-fade.
Untitled UI Pro: a 10,000+ component Figma UI kit · from $129 one-time
Untitled UI · Affiliate link
The code
<section class="cl-02" aria-label="Inner highlight and inner shadow layering demo">
<div class="cl-02__stage">
<div class="cl-02__hero">
<div class="cl-02__sphere" aria-hidden="true"></div>
<div class="cl-02__note">
<h2 class="cl-02__h">Two insets make the material</h2>
<p class="cl-02__lede">The highlight is the lit face, the shade is the far wall. Keep them diametrically opposed and the object holds one light source — even while the light orbits.</p>
<p class="cl-02__pill"><code>@property</code> registered lengths → animatable inset offsets</p>
</div>
</div>
<ul class="cl-02__set">
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--light" aria-hidden="true"></span>
<b class="cl-02__cap">Highlight only</b>
<span class="cl-02__sub">inflated · no thickness</span>
</li>
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--dark" aria-hidden="true"></span>
<b class="cl-02__cap">Shade only</b>
<span class="cl-02__sub">hollow · reads as a bowl</span>
</li>
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--both" aria-hidden="true"></span>
<b class="cl-02__cap">Both layers</b>
<span class="cl-02__sub">clay · pressable mass</span>
</li>
</ul>
</div>
</section><section class="cl-02" aria-label="Inner highlight and inner shadow layering demo">
<div class="cl-02__stage">
<div class="cl-02__hero">
<div class="cl-02__sphere" aria-hidden="true"></div>
<div class="cl-02__note">
<h2 class="cl-02__h">Two insets make the material</h2>
<p class="cl-02__lede">The highlight is the lit face, the shade is the far wall. Keep them diametrically opposed and the object holds one light source — even while the light orbits.</p>
<p class="cl-02__pill"><code>@property</code> registered lengths → animatable inset offsets</p>
</div>
</div>
<ul class="cl-02__set">
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--light" aria-hidden="true"></span>
<b class="cl-02__cap">Highlight only</b>
<span class="cl-02__sub">inflated · no thickness</span>
</li>
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--dark" aria-hidden="true"></span>
<b class="cl-02__cap">Shade only</b>
<span class="cl-02__sub">hollow · reads as a bowl</span>
</li>
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--both" aria-hidden="true"></span>
<b class="cl-02__cap">Both layers</b>
<span class="cl-02__sub">clay · pressable mass</span>
</li>
</ul>
</div>
</section>@property --cl-02-lx {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
@property --cl-02-ly {
syntax: '<length>';
inherits: false;
initial-value: 20px;
}
.cl-02,
.cl-02 *,
.cl-02 *::before,
.cl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cl-02 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--cl-02-bg);
--cl-02-bg: oklch(0.94 0.03 285);
--cl-02-clay: oklch(0.8 0.1 292);
--cl-02-shade: color-mix(in oklch,var(--cl-02-clay) 45%,oklch(0.38 0.13 290));
--cl-02-light: oklch(0.99 0.02 300/.95);
--cl-02-ink: oklch(0.3 0.06 292);
--cl-02-mut: oklch(0.52 0.05 292);
font-family: system-ui,-apple-system,'Segoe UI',sans-serif;
color: var(--cl-02-ink);
}
.cl-02__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(30px,5vw,52px);
padding: clamp(22px,5vw,60px);
background: radial-gradient(80% 60% at 78% 4%,oklch(0.97 0.04 300),transparent 62%);
}
.cl-02__hero {
display: grid;
gap: clamp(24px,4vw,44px);
grid-template-columns: 1fr;
justify-items: center;
align-items: center;
width: min(100%,860px);
text-align: center;
}
@media (min-width: 820px) {
.cl-02__hero {
grid-template-columns: auto 1fr;
text-align: left;
}
}
.cl-02__sphere {
width: clamp(168px,24vw,206px);
aspect-ratio: 1;
border-radius: 50%;
background: var(--cl-02-clay);
box-shadow: 0 26px 44px -14px var(--cl-02-shade),inset var(--cl-02-lx) var(--cl-02-ly) 30px -8px var(--cl-02-light),inset calc(var(--cl-02-lx) * -1) calc(var(--cl-02-ly) * -1) 30px -8px var(--cl-02-shade);
animation: cl-02-orbit 9s linear infinite;
}
@keyframes cl-02-orbit {
0% {
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
25% {
--cl-02-lx: 20px;
--cl-02-ly: 0px;
}
50% {
--cl-02-lx: 0px;
--cl-02-ly: -20px;
}
75% {
--cl-02-lx: -20px;
--cl-02-ly: 0px;
}
100% {
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
}
.cl-02__note {
display: grid;
gap: 12px;
max-width: 46ch;
}
.cl-02__h {
font-size: clamp(23px,3.4vw,33px);
font-weight: 780;
letter-spacing: -.02em;
text-wrap: balance;
}
.cl-02__lede {
font-size: 15px;
line-height: 1.65;
color: var(--cl-02-mut);
text-wrap: pretty;
}
.cl-02__pill {
justify-self: start;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
color: var(--cl-02-ink);
padding: 9px 16px;
border-radius: 99px;
background: color-mix(in oklch,var(--cl-02-bg) 80%,white);
box-shadow: inset 0 -6px 12px -8px var(--cl-02-shade),inset 0 8px 12px -6px var(--cl-02-light);
}
.cl-02__set {
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit,minmax(150px,1fr));
gap: clamp(16px,3vw,28px);
width: min(100%,760px);
}
.cl-02__item {
display: grid;
justify-items: center;
gap: 8px;
padding: 26px 18px 22px;
border-radius: 34px;
text-align: center;
background: color-mix(in oklch,var(--cl-02-bg) 86%,white);
box-shadow: 0 18px 34px -18px var(--cl-02-shade),inset 0 -10px 18px -12px var(--cl-02-shade),inset 0 12px 18px -10px var(--cl-02-light);
transition: translate .35s cubic-bezier(.34,1.3,.5,1);
}
.cl-02__item:hover {
translate: 0 -6px;
}
.cl-02__ball {
width: 82px;
height: 82px;
border-radius: 50%;
background: var(--cl-02-clay);
margin-bottom: 8px;
}
.cl-02__ball--light {
box-shadow: inset 0 20px 28px -8px var(--cl-02-light);
}
.cl-02__ball--dark {
box-shadow: inset 0 -20px 28px -8px var(--cl-02-shade);
}
.cl-02__ball--both {
box-shadow: inset 0 20px 28px -8px var(--cl-02-light),inset 0 -20px 28px -8px var(--cl-02-shade),0 12px 20px -10px var(--cl-02-shade);
}
.cl-02__cap {
font-size: 14.5px;
font-weight: 680;
letter-spacing: -.01em;
}
.cl-02__sub {
font-size: 12.5px;
color: var(--cl-02-mut);
}
@media (prefers-reduced-motion: reduce) {
.cl-02__sphere {
animation: none;
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
.cl-02 * {
transition-duration: .01ms !important;
}
}@property --cl-02-lx {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
@property --cl-02-ly {
syntax: '<length>';
inherits: false;
initial-value: 20px;
}
.cl-02,
.cl-02 *,
.cl-02 *::before,
.cl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cl-02 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--cl-02-bg);
--cl-02-bg: oklch(0.94 0.03 285);
--cl-02-clay: oklch(0.8 0.1 292);
--cl-02-shade: color-mix(in oklch,var(--cl-02-clay) 45%,oklch(0.38 0.13 290));
--cl-02-light: oklch(0.99 0.02 300/.95);
--cl-02-ink: oklch(0.3 0.06 292);
--cl-02-mut: oklch(0.52 0.05 292);
font-family: system-ui,-apple-system,'Segoe UI',sans-serif;
color: var(--cl-02-ink);
}
.cl-02__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(30px,5vw,52px);
padding: clamp(22px,5vw,60px);
background: radial-gradient(80% 60% at 78% 4%,oklch(0.97 0.04 300),transparent 62%);
}
.cl-02__hero {
display: grid;
gap: clamp(24px,4vw,44px);
grid-template-columns: 1fr;
justify-items: center;
align-items: center;
width: min(100%,860px);
text-align: center;
}
@media (min-width: 820px) {
.cl-02__hero {
grid-template-columns: auto 1fr;
text-align: left;
}
}
.cl-02__sphere {
width: clamp(168px,24vw,206px);
aspect-ratio: 1;
border-radius: 50%;
background: var(--cl-02-clay);
box-shadow: 0 26px 44px -14px var(--cl-02-shade),inset var(--cl-02-lx) var(--cl-02-ly) 30px -8px var(--cl-02-light),inset calc(var(--cl-02-lx) * -1) calc(var(--cl-02-ly) * -1) 30px -8px var(--cl-02-shade);
animation: cl-02-orbit 9s linear infinite;
}
@keyframes cl-02-orbit {
0% {
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
25% {
--cl-02-lx: 20px;
--cl-02-ly: 0px;
}
50% {
--cl-02-lx: 0px;
--cl-02-ly: -20px;
}
75% {
--cl-02-lx: -20px;
--cl-02-ly: 0px;
}
100% {
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
}
.cl-02__note {
display: grid;
gap: 12px;
max-width: 46ch;
}
.cl-02__h {
font-size: clamp(23px,3.4vw,33px);
font-weight: 780;
letter-spacing: -.02em;
text-wrap: balance;
}
.cl-02__lede {
font-size: 15px;
line-height: 1.65;
color: var(--cl-02-mut);
text-wrap: pretty;
}
.cl-02__pill {
justify-self: start;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
color: var(--cl-02-ink);
padding: 9px 16px;
border-radius: 99px;
background: color-mix(in oklch,var(--cl-02-bg) 80%,white);
box-shadow: inset 0 -6px 12px -8px var(--cl-02-shade),inset 0 8px 12px -6px var(--cl-02-light);
}
.cl-02__set {
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit,minmax(150px,1fr));
gap: clamp(16px,3vw,28px);
width: min(100%,760px);
}
.cl-02__item {
display: grid;
justify-items: center;
gap: 8px;
padding: 26px 18px 22px;
border-radius: 34px;
text-align: center;
background: color-mix(in oklch,var(--cl-02-bg) 86%,white);
box-shadow: 0 18px 34px -18px var(--cl-02-shade),inset 0 -10px 18px -12px var(--cl-02-shade),inset 0 12px 18px -10px var(--cl-02-light);
transition: translate .35s cubic-bezier(.34,1.3,.5,1);
}
.cl-02__item:hover {
translate: 0 -6px;
}
.cl-02__ball {
width: 82px;
height: 82px;
border-radius: 50%;
background: var(--cl-02-clay);
margin-bottom: 8px;
}
.cl-02__ball--light {
box-shadow: inset 0 20px 28px -8px var(--cl-02-light);
}
.cl-02__ball--dark {
box-shadow: inset 0 -20px 28px -8px var(--cl-02-shade);
}
.cl-02__ball--both {
box-shadow: inset 0 20px 28px -8px var(--cl-02-light),inset 0 -20px 28px -8px var(--cl-02-shade),0 12px 20px -10px var(--cl-02-shade);
}
.cl-02__cap {
font-size: 14.5px;
font-weight: 680;
letter-spacing: -.01em;
}
.cl-02__sub {
font-size: 12.5px;
color: var(--cl-02-mut);
}
@media (prefers-reduced-motion: reduce) {
.cl-02__sphere {
animation: none;
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
.cl-02 * {
transition-duration: .01ms !important;
}
}Here's a working CSS Claymorphism 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: Inner Highlight and Inner Shadow Layering
Source: https://codefronts.com/design-styles/css-claymorphism/inner-highlight-and-inner-shadow-layering/
Isolate the two inset layers and the material becomes obvious: highlight alone looks inflated, shade alone looks hollow, together they look like clay you could press a thumb into. Three spheres show each state, and the hero sphere animates its light around the object using @property-registered lengths — a genuinely animatable inset shadow, not a cross-fade.
## HTML
```html
<section class="cl-02" aria-label="Inner highlight and inner shadow layering demo">
<div class="cl-02__stage">
<div class="cl-02__hero">
<div class="cl-02__sphere" aria-hidden="true"></div>
<div class="cl-02__note">
<h2 class="cl-02__h">Two insets make the material</h2>
<p class="cl-02__lede">The highlight is the lit face, the shade is the far wall. Keep them diametrically opposed and the object holds one light source — even while the light orbits.</p>
<p class="cl-02__pill"><code>@property</code> registered lengths → animatable inset offsets</p>
</div>
</div>
<ul class="cl-02__set">
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--light" aria-hidden="true"></span>
<b class="cl-02__cap">Highlight only</b>
<span class="cl-02__sub">inflated · no thickness</span>
</li>
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--dark" aria-hidden="true"></span>
<b class="cl-02__cap">Shade only</b>
<span class="cl-02__sub">hollow · reads as a bowl</span>
</li>
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--both" aria-hidden="true"></span>
<b class="cl-02__cap">Both layers</b>
<span class="cl-02__sub">clay · pressable mass</span>
</li>
</ul>
</div>
</section>
```
## CSS
```css
@property --cl-02-lx {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
@property --cl-02-ly {
syntax: '<length>';
inherits: false;
initial-value: 20px;
}
.cl-02,
.cl-02 *,
.cl-02 *::before,
.cl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cl-02 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--cl-02-bg);
--cl-02-bg: oklch(0.94 0.03 285);
--cl-02-clay: oklch(0.8 0.1 292);
--cl-02-shade: color-mix(in oklch,var(--cl-02-clay) 45%,oklch(0.38 0.13 290));
--cl-02-light: oklch(0.99 0.02 300/.95);
--cl-02-ink: oklch(0.3 0.06 292);
--cl-02-mut: oklch(0.52 0.05 292);
font-family: system-ui,-apple-system,'Segoe UI',sans-serif;
color: var(--cl-02-ink);
}
.cl-02__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(30px,5vw,52px);
padding: clamp(22px,5vw,60px);
background: radial-gradient(80% 60% at 78% 4%,oklch(0.97 0.04 300),transparent 62%);
}
.cl-02__hero {
display: grid;
gap: clamp(24px,4vw,44px);
grid-template-columns: 1fr;
justify-items: center;
align-items: center;
width: min(100%,860px);
text-align: center;
}
@media (min-width: 820px) {
.cl-02__hero {
grid-template-columns: auto 1fr;
text-align: left;
}
}
.cl-02__sphere {
width: clamp(168px,24vw,206px);
aspect-ratio: 1;
border-radius: 50%;
background: var(--cl-02-clay);
box-shadow: 0 26px 44px -14px var(--cl-02-shade),inset var(--cl-02-lx) var(--cl-02-ly) 30px -8px var(--cl-02-light),inset calc(var(--cl-02-lx) * -1) calc(var(--cl-02-ly) * -1) 30px -8px var(--cl-02-shade);
animation: cl-02-orbit 9s linear infinite;
}
@keyframes cl-02-orbit {
0% {
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
25% {
--cl-02-lx: 20px;
--cl-02-ly: 0px;
}
50% {
--cl-02-lx: 0px;
--cl-02-ly: -20px;
}
75% {
--cl-02-lx: -20px;
--cl-02-ly: 0px;
}
100% {
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
}
.cl-02__note {
display: grid;
gap: 12px;
max-width: 46ch;
}
.cl-02__h {
font-size: clamp(23px,3.4vw,33px);
font-weight: 780;
letter-spacing: -.02em;
text-wrap: balance;
}
.cl-02__lede {
font-size: 15px;
line-height: 1.65;
color: var(--cl-02-mut);
text-wrap: pretty;
}
.cl-02__pill {
justify-self: start;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
color: var(--cl-02-ink);
padding: 9px 16px;
border-radius: 99px;
background: color-mix(in oklch,var(--cl-02-bg) 80%,white);
box-shadow: inset 0 -6px 12px -8px var(--cl-02-shade),inset 0 8px 12px -6px var(--cl-02-light);
}
.cl-02__set {
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit,minmax(150px,1fr));
gap: clamp(16px,3vw,28px);
width: min(100%,760px);
}
.cl-02__item {
display: grid;
justify-items: center;
gap: 8px;
padding: 26px 18px 22px;
border-radius: 34px;
text-align: center;
background: color-mix(in oklch,var(--cl-02-bg) 86%,white);
box-shadow: 0 18px 34px -18px var(--cl-02-shade),inset 0 -10px 18px -12px var(--cl-02-shade),inset 0 12px 18px -10px var(--cl-02-light);
transition: translate .35s cubic-bezier(.34,1.3,.5,1);
}
.cl-02__item:hover {
translate: 0 -6px;
}
.cl-02__ball {
width: 82px;
height: 82px;
border-radius: 50%;
background: var(--cl-02-clay);
margin-bottom: 8px;
}
.cl-02__ball--light {
box-shadow: inset 0 20px 28px -8px var(--cl-02-light);
}
.cl-02__ball--dark {
box-shadow: inset 0 -20px 28px -8px var(--cl-02-shade);
}
.cl-02__ball--both {
box-shadow: inset 0 20px 28px -8px var(--cl-02-light),inset 0 -20px 28px -8px var(--cl-02-shade),0 12px 20px -10px var(--cl-02-shade);
}
.cl-02__cap {
font-size: 14.5px;
font-weight: 680;
letter-spacing: -.01em;
}
.cl-02__sub {
font-size: 12.5px;
color: var(--cl-02-mut);
}
@media (prefers-reduced-motion: reduce) {
.cl-02__sphere {
animation: none;
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
.cl-02 * {
transition-duration: .01ms !important;
}
}
```Here's a working CSS Claymorphism 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: Inner Highlight and Inner Shadow Layering
Source: https://codefronts.com/design-styles/css-claymorphism/inner-highlight-and-inner-shadow-layering/
Isolate the two inset layers and the material becomes obvious: highlight alone looks inflated, shade alone looks hollow, together they look like clay you could press a thumb into. Three spheres show each state, and the hero sphere animates its light around the object using @property-registered lengths — a genuinely animatable inset shadow, not a cross-fade.
## HTML
```html
<section class="cl-02" aria-label="Inner highlight and inner shadow layering demo">
<div class="cl-02__stage">
<div class="cl-02__hero">
<div class="cl-02__sphere" aria-hidden="true"></div>
<div class="cl-02__note">
<h2 class="cl-02__h">Two insets make the material</h2>
<p class="cl-02__lede">The highlight is the lit face, the shade is the far wall. Keep them diametrically opposed and the object holds one light source — even while the light orbits.</p>
<p class="cl-02__pill"><code>@property</code> registered lengths → animatable inset offsets</p>
</div>
</div>
<ul class="cl-02__set">
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--light" aria-hidden="true"></span>
<b class="cl-02__cap">Highlight only</b>
<span class="cl-02__sub">inflated · no thickness</span>
</li>
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--dark" aria-hidden="true"></span>
<b class="cl-02__cap">Shade only</b>
<span class="cl-02__sub">hollow · reads as a bowl</span>
</li>
<li class="cl-02__item">
<span class="cl-02__ball cl-02__ball--both" aria-hidden="true"></span>
<b class="cl-02__cap">Both layers</b>
<span class="cl-02__sub">clay · pressable mass</span>
</li>
</ul>
</div>
</section>
```
## CSS
```css
@property --cl-02-lx {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
@property --cl-02-ly {
syntax: '<length>';
inherits: false;
initial-value: 20px;
}
.cl-02,
.cl-02 *,
.cl-02 *::before,
.cl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cl-02 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--cl-02-bg);
--cl-02-bg: oklch(0.94 0.03 285);
--cl-02-clay: oklch(0.8 0.1 292);
--cl-02-shade: color-mix(in oklch,var(--cl-02-clay) 45%,oklch(0.38 0.13 290));
--cl-02-light: oklch(0.99 0.02 300/.95);
--cl-02-ink: oklch(0.3 0.06 292);
--cl-02-mut: oklch(0.52 0.05 292);
font-family: system-ui,-apple-system,'Segoe UI',sans-serif;
color: var(--cl-02-ink);
}
.cl-02__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(30px,5vw,52px);
padding: clamp(22px,5vw,60px);
background: radial-gradient(80% 60% at 78% 4%,oklch(0.97 0.04 300),transparent 62%);
}
.cl-02__hero {
display: grid;
gap: clamp(24px,4vw,44px);
grid-template-columns: 1fr;
justify-items: center;
align-items: center;
width: min(100%,860px);
text-align: center;
}
@media (min-width: 820px) {
.cl-02__hero {
grid-template-columns: auto 1fr;
text-align: left;
}
}
.cl-02__sphere {
width: clamp(168px,24vw,206px);
aspect-ratio: 1;
border-radius: 50%;
background: var(--cl-02-clay);
box-shadow: 0 26px 44px -14px var(--cl-02-shade),inset var(--cl-02-lx) var(--cl-02-ly) 30px -8px var(--cl-02-light),inset calc(var(--cl-02-lx) * -1) calc(var(--cl-02-ly) * -1) 30px -8px var(--cl-02-shade);
animation: cl-02-orbit 9s linear infinite;
}
@keyframes cl-02-orbit {
0% {
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
25% {
--cl-02-lx: 20px;
--cl-02-ly: 0px;
}
50% {
--cl-02-lx: 0px;
--cl-02-ly: -20px;
}
75% {
--cl-02-lx: -20px;
--cl-02-ly: 0px;
}
100% {
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
}
.cl-02__note {
display: grid;
gap: 12px;
max-width: 46ch;
}
.cl-02__h {
font-size: clamp(23px,3.4vw,33px);
font-weight: 780;
letter-spacing: -.02em;
text-wrap: balance;
}
.cl-02__lede {
font-size: 15px;
line-height: 1.65;
color: var(--cl-02-mut);
text-wrap: pretty;
}
.cl-02__pill {
justify-self: start;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
color: var(--cl-02-ink);
padding: 9px 16px;
border-radius: 99px;
background: color-mix(in oklch,var(--cl-02-bg) 80%,white);
box-shadow: inset 0 -6px 12px -8px var(--cl-02-shade),inset 0 8px 12px -6px var(--cl-02-light);
}
.cl-02__set {
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit,minmax(150px,1fr));
gap: clamp(16px,3vw,28px);
width: min(100%,760px);
}
.cl-02__item {
display: grid;
justify-items: center;
gap: 8px;
padding: 26px 18px 22px;
border-radius: 34px;
text-align: center;
background: color-mix(in oklch,var(--cl-02-bg) 86%,white);
box-shadow: 0 18px 34px -18px var(--cl-02-shade),inset 0 -10px 18px -12px var(--cl-02-shade),inset 0 12px 18px -10px var(--cl-02-light);
transition: translate .35s cubic-bezier(.34,1.3,.5,1);
}
.cl-02__item:hover {
translate: 0 -6px;
}
.cl-02__ball {
width: 82px;
height: 82px;
border-radius: 50%;
background: var(--cl-02-clay);
margin-bottom: 8px;
}
.cl-02__ball--light {
box-shadow: inset 0 20px 28px -8px var(--cl-02-light);
}
.cl-02__ball--dark {
box-shadow: inset 0 -20px 28px -8px var(--cl-02-shade);
}
.cl-02__ball--both {
box-shadow: inset 0 20px 28px -8px var(--cl-02-light),inset 0 -20px 28px -8px var(--cl-02-shade),0 12px 20px -10px var(--cl-02-shade);
}
.cl-02__cap {
font-size: 14.5px;
font-weight: 680;
letter-spacing: -.01em;
}
.cl-02__sub {
font-size: 12.5px;
color: var(--cl-02-mut);
}
@media (prefers-reduced-motion: reduce) {
.cl-02__sphere {
animation: none;
--cl-02-lx: 0px;
--cl-02-ly: 20px;
}
.cl-02 * {
transition-duration: .01ms !important;
}
}
```How this works
inset shadows paint inside the padding box, from the edge inwards. That makes them the material layer: the outer drop tells you where the object is, the insets tell you what it is made of.
/* highlight only — inflated balloon */
box-shadow: inset 0 20px 28px -8px var(--light);
/* shade only — hollow bowl */
box-shadow: inset 0 -20px 28px -8px var(--shade);
/* both — clay */
box-shadow: inset 0 20px 28px -8px var(--light),
inset 0 -20px 28px -8px var(--shade);Order matters: shadow lists paint first-on-top, so the highlight is written first and sits above the shade where they overlap. Swap them and the sheen gets muddied.
The hero sphere animates its light direction, which normally is impossible — box-shadow only interpolates as a whole. Registering the offsets as typed custom properties makes each one an animatable number:
@property --lx { syntax:'<length>'; inherits:false; initial-value:0px; }
@property --ly { syntax:'<length>'; inherits:false; initial-value:18px; }
.sphere{ box-shadow: inset var(--lx) var(--ly) 30px -8px var(--light),
inset calc(var(--lx) * -1) calc(var(--ly) * -1) 30px -8px var(--shade); }
@keyframes orbit{ 25%{--lx:16px;--ly:0px} 50%{--lx:0px;--ly:-18px} 75%{--lx:-16px;--ly:0px} }Because the shade offsets are the negated highlight offsets, the two insets stay diametrically opposed for the entire orbit — the object keeps a single, coherent light source while it rotates. Registered properties are the only way to get smooth interpolation here; unregistered var() values snap between keyframes.
Make it yours
- Matte vs glossy: cut the highlight blur in half and raise its alpha for a tight specular sheen (glossy vinyl); widen the blur and drop the alpha for chalky matte clay.
- Pressed-in state: swap the two colors (shade on top, highlight below) and the same element reads as debossed — that is your active state for demo 09.
- Metallic clay: add a third inset ring,
inset 0 0 0 1px color-mix(in oklch, var(--light) 60%, transparent), for a crisp rim light. - Stop the orbit and freeze a direction by setting
--cl-02-lx/-lyon the element and removing the animation — the same declaration then serves as your static token.
Gotchas — read before shipping
- Unregistered custom properties do not animate — they flip at the keyframe. Any length you want to tween must go through
@propertywith a<length>syntax. - inset shadows are clipped by
border-radiusbut ignoreoverflow; a child image with square corners will still poke through — clip the child with the same radius. - Two insets on a small element (under ~48px) collapse into mush. Below that size use a single highlight and let the outer drop carry the depth.
- The highlight must be near-white but not pure white on saturated fills — pure white desaturates the hue and the object looks dusty. Use color-mix with the fill hue.
- Registered-property animations run on the main thread; keep them to a handful of decorative elements, never on a scrolling list.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 128+ | 114+ |
Floor set by oklch(), color-mix(), @property, text-wrap as this demo is written. The core technique itself goes back further — Chrome 85+.
@property landed in Chrome 85, Safari 16.4 and Firefox 128 — with a static-fallback initial-value the animation simply does not run on older engines while the layered shadows still render correctly.