51 / 51
All 51 in 51 Tailwind CSS Buttons
- 01Tailwind Submit Button (Form Primary)
- 02Tailwind Primary and Secondary Button Pair
- 03Tailwind Ghost / Outline Button
- 04Tailwind Icon-Only Button with Accessible Tooltip
- 05Tailwind Gradient CTA Button
- 06Tailwind Loading Spinner Button (State Transition)
- 07Tailwind Disabled Button State (Full State Reference)
- 08Tailwind Button Group Component (Segmented Radio-Like)
- 09Tailwind Add to Cart Button
- 10Tailwind Download Progress Button
- 11Tailwind Like Heart Button
- 12Tailwind Confirm Delete Button
- 13Tailwind Play Pause Button
- 14Tailwind Subscribe Confetti Button
- 15Tailwind Magnetic Mercury Ripple Button
- 16Tailwind Brutalist Glitch Button
- 17Tailwind Neon Plasma Burst Button
- 18Tailwind Clay Press Button
- 19Tailwind Editorial Ink Spread Button
- 20Tailwind Particle Shard Burst Button
- 21Tailwind Brushed Brass Plate Button
- 22Tailwind Washi Paper Crane Button
- 23Tailwind Frosted Ice Cube Button
- 24Tailwind Vintage Typewriter Key Button
- 25Tailwind Lacquered Walnut Wood Button
- 26Tailwind Pharma Gel Capsule Button
- 27Tailwind Aged Copper Coin Button
- 28Tailwind Origami Fortune Cookie Button
- 29Tailwind Holographic Foil Sticker Button
- 30Tailwind Brutalist Concrete Slab Button
- 31Tailwind Mercury Bead Morph Button
- 32Tailwind Soft Pill Modern SaaS Button
- 33Tailwind Masking Tape Label Button
- 34Tailwind Obsidian Glass Shard Button
- 35Tailwind Wine Velvet Ribbon Button
- 36Tailwind LED Segment Display Button
- 37Tailwind Cassette Mixtape Label Button
- 38Tailwind Chalkboard Menu Button
- 39Tailwind Vinyl Record Sleeve Ripple Button
- 40Tailwind Polaroid Snapshot Button
- 41Tailwind Airline Boarding Pass Button
- 42Tailwind Aged Brass Subway Token Button
- 43Tailwind Vintage Rx Pharmacy Label Button
- 44Tailwind Bakelite Radio Dial Toggle Button
- 45Tailwind Regal Postage Stamp Button
- 46Tailwind Victorian Cigar Band Foil Button
- 47Tailwind Vintage Calculator Key Button
- 48Tailwind Toolbox Latch Spotlight Button
- 49Tailwind Receipt Paper Total Count-Up Button
- 50Tailwind IBM Punch Card Magnetic Follow Button
- 51Tailwind Brass Periscope Lens Button
Tailwind Brass Periscope Lens Button
A brass periscope objective: a knurled bezel, an engraved focus scale, and a six-blade iris that opens on hover to reveal the glass — each blade a clipped triangle rotating about the optical centre, with a reflection sweep across the coated lens.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tb-51 w-full min-h-svh grid place-items-center font-tb51-sans p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" aria-label="Open the viewfinder" class="group cursor-pointer border-0 bg-transparent p-0 [-webkit-tap-highlight-color:transparent] focus-visible:outline-none disabled:cursor-not-allowed disabled:grayscale-[0.7] disabled:brightness-[0.85]">
<span aria-hidden="true" class="tb-51__bezel relative grid place-items-center size-38 rounded-full shadow-[inset_0_2px_3px_#ffffff73,inset_0_-3px_7px_#0000008c,0_18px_28px_-12px_#000000d9] transition-[rotate,filter] duration-[550ms] ease-tb51-out group-hover:brightness-105 group-active:rotate-6 group-focus-visible:outline-2 group-focus-visible:outline-offset-[5px] group-focus-visible:outline-tb51-brass-hi motion-reduce:transition-none">
<span class="absolute inset-0 rounded-full bg-[repeating-conic-gradient(from_0deg,#ffffff38_0_1.4deg,#00000047_1.4deg_3deg)] [mask-image:radial-gradient(farthest-side,transparent_86%,#000_88%)]"></span>
<span class="tb-51__scale absolute inset-[9%] rounded-full opacity-75 bg-[repeating-conic-gradient(from_-90deg,var(--color-tb51-brass-lo)_0_1deg,transparent_1deg_15deg)] [mask-image:radial-gradient(farthest-side,transparent_82%,#000_84%)]"></span>
<span class="tb-51__barrel relative grid place-items-center w-[66%] h-[66%] rounded-full overflow-hidden shadow-[inset_0_0_0_3px_var(--color-tb51-barrel-ring),inset_0_4px_10px_#000000e6]">
<span class="tb-51__glass absolute inset-0 rounded-full z-10"></span>
<span class="absolute inset-0 z-20 rounded-full">
<span style="--i:0" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:1" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:2" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:3" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:4" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:5" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
</span>
<span class="absolute -inset-[30%] z-30 pointer-events-none -translate-x-[70%] bg-[linear-gradient(104deg,transparent_42%,rgba(255,255,255,0.42)_50%,transparent_58%)] transition-transform duration-[900ms] ease-tb51-out group-hover:translate-x-[70%] group-focus-visible:translate-x-[70%] motion-reduce:hidden"></span>
</span>
</span>
</button>
</div><div class="tb-51 w-full min-h-svh grid place-items-center font-tb51-sans p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" aria-label="Open the viewfinder" class="group cursor-pointer border-0 bg-transparent p-0 [-webkit-tap-highlight-color:transparent] focus-visible:outline-none disabled:cursor-not-allowed disabled:grayscale-[0.7] disabled:brightness-[0.85]">
<span aria-hidden="true" class="tb-51__bezel relative grid place-items-center size-38 rounded-full shadow-[inset_0_2px_3px_#ffffff73,inset_0_-3px_7px_#0000008c,0_18px_28px_-12px_#000000d9] transition-[rotate,filter] duration-[550ms] ease-tb51-out group-hover:brightness-105 group-active:rotate-6 group-focus-visible:outline-2 group-focus-visible:outline-offset-[5px] group-focus-visible:outline-tb51-brass-hi motion-reduce:transition-none">
<span class="absolute inset-0 rounded-full bg-[repeating-conic-gradient(from_0deg,#ffffff38_0_1.4deg,#00000047_1.4deg_3deg)] [mask-image:radial-gradient(farthest-side,transparent_86%,#000_88%)]"></span>
<span class="tb-51__scale absolute inset-[9%] rounded-full opacity-75 bg-[repeating-conic-gradient(from_-90deg,var(--color-tb51-brass-lo)_0_1deg,transparent_1deg_15deg)] [mask-image:radial-gradient(farthest-side,transparent_82%,#000_84%)]"></span>
<span class="tb-51__barrel relative grid place-items-center w-[66%] h-[66%] rounded-full overflow-hidden shadow-[inset_0_0_0_3px_var(--color-tb51-barrel-ring),inset_0_4px_10px_#000000e6]">
<span class="tb-51__glass absolute inset-0 rounded-full z-10"></span>
<span class="absolute inset-0 z-20 rounded-full">
<span style="--i:0" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:1" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:2" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:3" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:4" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:5" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
</span>
<span class="absolute -inset-[30%] z-30 pointer-events-none -translate-x-[70%] bg-[linear-gradient(104deg,transparent_42%,rgba(255,255,255,0.42)_50%,transparent_58%)] transition-transform duration-[900ms] ease-tb51-out group-hover:translate-x-[70%] group-focus-visible:translate-x-[70%] motion-reduce:hidden"></span>
</span>
</span>
</button>
</div>@import "tailwindcss";
@theme {
--color-tb51-bg: #141613;
--color-tb51-bg: oklch(0.18 0.01 130);
--color-tb51-brass: #b28c4c;
--color-tb51-brass: oklch(0.66 0.08 84);
--color-tb51-brass-hi: #e8cc90;
--color-tb51-brass-hi: oklch(0.86 0.08 88);
--color-tb51-brass-lo: #6f5426;
--color-tb51-brass-lo: oklch(0.45 0.07 82);
--color-tb51-barrel-ring: #4a3819;
--color-tb51-barrel-ring: oklch(0.34 0.055 82);
--color-tb51-blade: #2a2b28;
--color-tb51-blade: oklch(0.28 0.004 130);
--color-tb51-blade-hi: #4a4b46;
--color-tb51-blade-hi: oklch(0.42 0.004 130);
--color-tb51-glass: #1a3b46;
--color-tb51-glass: oklch(0.32 0.05 220);
--color-tb51-coat: #4fd6c9;
--color-tb51-coat: oklch(0.82 0.12 182);
--font-tb51-sans: "Oswald", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb51-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-51 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(30rem 20rem at 50% 44%, color-mix(in oklab, var(--color-tb51-brass) 14%, transparent), transparent 74%), var(--color-tb51-bg);
}
.tb-51__bezel {
background: radial-gradient(circle at 34% 26%, var(--color-tb51-brass-hi), transparent 56%), linear-gradient(150deg, var(--color-tb51-brass), var(--color-tb51-brass-lo));
}
.tb-51__barrel {
background: radial-gradient(circle at 50% 50%, #101210, #050605);
}
/* COATED GLASS: coating tint + specular crescent + catchlight. The three together
are what stop it reading as a coloured circle. */
.tb-51__glass {
background: radial-gradient(circle at 62% 70%, color-mix(in oklab, var(--color-tb51-coat) 55%, transparent), transparent 58%), radial-gradient(circle at 34% 26%, rgba(255,255,255,.5), transparent 42%), radial-gradient(circle at 50% 50%, var(--color-tb51-glass), #08181d 78%);
}
/* BLADE ANGLES: each blade carries its own --i inline; the rotation composes it.
rotate-[calc(var(--i)*60deg)] as a utility would compile ONE static rule that
all six blades share — the same trap as the shard button's angle. */
.tb-51__blade {
background: linear-gradient(120deg, var(--color-tb51-blade-hi), var(--color-tb51-blade));
transform-origin: 50% 50%;
rotate: calc(var(--i) * 60deg);
}
.tb-51 button:is(:hover, :focus-visible) .tb-51__blade {
rotate: calc(var(--i) * 60deg + 44deg);
}
/* Safari ≤15.3 shims for the two bezel ring masks. */
.tb-51__bezel > span:first-child {
-webkit-mask-image: radial-gradient(farthest-side, transparent 86%, #000 88%);
}
.tb-51__scale {
-webkit-mask-image: radial-gradient(farthest-side, transparent 82%, #000 84%);
}
@media (prefers-color-scheme: light) {
.tb-51:not([data-theme="dark"]) {
--color-tb51-bg: oklch(0.23 0.012 130);
}
}
.tb-51[data-theme="light"] {
--color-tb51-bg: oklch(0.23 0.012 130);
}
@media (forced-colors: active) {
.tb-51__bezel {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-51__bezel > span:first-child,
.tb-51__scale,
.tb-51__barrel > span:last-child {
display: none;
}
.tb-51__barrel {
background: Canvas;
box-shadow: inset 0 0 0 2px CanvasText;
}
.tb-51__blade {
background: ButtonText;
}
}@import "tailwindcss";
@theme {
--color-tb51-bg: #141613;
--color-tb51-bg: oklch(0.18 0.01 130);
--color-tb51-brass: #b28c4c;
--color-tb51-brass: oklch(0.66 0.08 84);
--color-tb51-brass-hi: #e8cc90;
--color-tb51-brass-hi: oklch(0.86 0.08 88);
--color-tb51-brass-lo: #6f5426;
--color-tb51-brass-lo: oklch(0.45 0.07 82);
--color-tb51-barrel-ring: #4a3819;
--color-tb51-barrel-ring: oklch(0.34 0.055 82);
--color-tb51-blade: #2a2b28;
--color-tb51-blade: oklch(0.28 0.004 130);
--color-tb51-blade-hi: #4a4b46;
--color-tb51-blade-hi: oklch(0.42 0.004 130);
--color-tb51-glass: #1a3b46;
--color-tb51-glass: oklch(0.32 0.05 220);
--color-tb51-coat: #4fd6c9;
--color-tb51-coat: oklch(0.82 0.12 182);
--font-tb51-sans: "Oswald", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb51-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-51 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(30rem 20rem at 50% 44%, color-mix(in oklab, var(--color-tb51-brass) 14%, transparent), transparent 74%), var(--color-tb51-bg);
}
.tb-51__bezel {
background: radial-gradient(circle at 34% 26%, var(--color-tb51-brass-hi), transparent 56%), linear-gradient(150deg, var(--color-tb51-brass), var(--color-tb51-brass-lo));
}
.tb-51__barrel {
background: radial-gradient(circle at 50% 50%, #101210, #050605);
}
/* COATED GLASS: coating tint + specular crescent + catchlight. The three together
are what stop it reading as a coloured circle. */
.tb-51__glass {
background: radial-gradient(circle at 62% 70%, color-mix(in oklab, var(--color-tb51-coat) 55%, transparent), transparent 58%), radial-gradient(circle at 34% 26%, rgba(255,255,255,.5), transparent 42%), radial-gradient(circle at 50% 50%, var(--color-tb51-glass), #08181d 78%);
}
/* BLADE ANGLES: each blade carries its own --i inline; the rotation composes it.
rotate-[calc(var(--i)*60deg)] as a utility would compile ONE static rule that
all six blades share — the same trap as the shard button's angle. */
.tb-51__blade {
background: linear-gradient(120deg, var(--color-tb51-blade-hi), var(--color-tb51-blade));
transform-origin: 50% 50%;
rotate: calc(var(--i) * 60deg);
}
.tb-51 button:is(:hover, :focus-visible) .tb-51__blade {
rotate: calc(var(--i) * 60deg + 44deg);
}
/* Safari ≤15.3 shims for the two bezel ring masks. */
.tb-51__bezel > span:first-child {
-webkit-mask-image: radial-gradient(farthest-side, transparent 86%, #000 88%);
}
.tb-51__scale {
-webkit-mask-image: radial-gradient(farthest-side, transparent 82%, #000 84%);
}
@media (prefers-color-scheme: light) {
.tb-51:not([data-theme="dark"]) {
--color-tb51-bg: oklch(0.23 0.012 130);
}
}
.tb-51[data-theme="light"] {
--color-tb51-bg: oklch(0.23 0.012 130);
}
@media (forced-colors: active) {
.tb-51__bezel {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-51__bezel > span:first-child,
.tb-51__scale,
.tb-51__barrel > span:last-child {
display: none;
}
.tb-51__barrel {
background: Canvas;
box-shadow: inset 0 0 0 2px CanvasText;
}
.tb-51__blade {
background: ButtonText;
}
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tb51-bg: #141613;
--color-tb51-bg: oklch(0.18 0.01 130);
--color-tb51-brass: #b28c4c;
--color-tb51-brass: oklch(0.66 0.08 84);
--color-tb51-brass-hi: #e8cc90;
--color-tb51-brass-hi: oklch(0.86 0.08 88);
--color-tb51-brass-lo: #6f5426;
--color-tb51-brass-lo: oklch(0.45 0.07 82);
--color-tb51-barrel-ring: #4a3819;
--color-tb51-barrel-ring: oklch(0.34 0.055 82);
--color-tb51-blade: #2a2b28;
--color-tb51-blade: oklch(0.28 0.004 130);
--color-tb51-blade-hi: #4a4b46;
--color-tb51-blade-hi: oklch(0.42 0.004 130);
--color-tb51-glass: #1a3b46;
--color-tb51-glass: oklch(0.32 0.05 220);
--color-tb51-coat: #4fd6c9;
--color-tb51-coat: oklch(0.82 0.12 182);
--font-tb51-sans: "Oswald", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb51-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-51 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(30rem 20rem at 50% 44%, color-mix(in oklab, var(--color-tb51-brass) 14%, transparent), transparent 74%), var(--color-tb51-bg);
}
.tb-51__bezel {
background: radial-gradient(circle at 34% 26%, var(--color-tb51-brass-hi), transparent 56%), linear-gradient(150deg, var(--color-tb51-brass), var(--color-tb51-brass-lo));
}
.tb-51__barrel {
background: radial-gradient(circle at 50% 50%, #101210, #050605);
}
/* COATED GLASS: coating tint + specular crescent + catchlight. The three together
are what stop it reading as a coloured circle. */
.tb-51__glass {
background: radial-gradient(circle at 62% 70%, color-mix(in oklab, var(--color-tb51-coat) 55%, transparent), transparent 58%), radial-gradient(circle at 34% 26%, rgba(255,255,255,.5), transparent 42%), radial-gradient(circle at 50% 50%, var(--color-tb51-glass), #08181d 78%);
}
/* BLADE ANGLES: each blade carries its own --i inline; the rotation composes it.
rotate-[calc(var(--i)*60deg)] as a utility would compile ONE static rule that
all six blades share — the same trap as the shard button's angle. */
.tb-51__blade {
background: linear-gradient(120deg, var(--color-tb51-blade-hi), var(--color-tb51-blade));
transform-origin: 50% 50%;
rotate: calc(var(--i) * 60deg);
}
.tb-51 button:is(:hover, :focus-visible) .tb-51__blade {
rotate: calc(var(--i) * 60deg + 44deg);
}
/* Safari ≤15.3 shims for the two bezel ring masks. */
.tb-51__bezel > span:first-child {
-webkit-mask-image: radial-gradient(farthest-side, transparent 86%, #000 88%);
}
.tb-51__scale {
-webkit-mask-image: radial-gradient(farthest-side, transparent 82%, #000 84%);
}
@media (prefers-color-scheme: light) {
.tb-51:not([data-theme="dark"]) {
--color-tb51-bg: oklch(0.23 0.012 130);
}
}
.tb-51[data-theme="light"] {
--color-tb51-bg: oklch(0.23 0.012 130);
}
@media (forced-colors: active) {
.tb-51__bezel {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-51__bezel > span:first-child,
.tb-51__scale,
.tb-51__barrel > span:last-child {
display: none;
}
.tb-51__barrel {
background: Canvas;
box-shadow: inset 0 0 0 2px CanvasText;
}
.tb-51__blade {
background: ButtonText;
}
}@import "tailwindcss";
@theme {
--color-tb51-bg: #141613;
--color-tb51-bg: oklch(0.18 0.01 130);
--color-tb51-brass: #b28c4c;
--color-tb51-brass: oklch(0.66 0.08 84);
--color-tb51-brass-hi: #e8cc90;
--color-tb51-brass-hi: oklch(0.86 0.08 88);
--color-tb51-brass-lo: #6f5426;
--color-tb51-brass-lo: oklch(0.45 0.07 82);
--color-tb51-barrel-ring: #4a3819;
--color-tb51-barrel-ring: oklch(0.34 0.055 82);
--color-tb51-blade: #2a2b28;
--color-tb51-blade: oklch(0.28 0.004 130);
--color-tb51-blade-hi: #4a4b46;
--color-tb51-blade-hi: oklch(0.42 0.004 130);
--color-tb51-glass: #1a3b46;
--color-tb51-glass: oklch(0.32 0.05 220);
--color-tb51-coat: #4fd6c9;
--color-tb51-coat: oklch(0.82 0.12 182);
--font-tb51-sans: "Oswald", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb51-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-51 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(30rem 20rem at 50% 44%, color-mix(in oklab, var(--color-tb51-brass) 14%, transparent), transparent 74%), var(--color-tb51-bg);
}
.tb-51__bezel {
background: radial-gradient(circle at 34% 26%, var(--color-tb51-brass-hi), transparent 56%), linear-gradient(150deg, var(--color-tb51-brass), var(--color-tb51-brass-lo));
}
.tb-51__barrel {
background: radial-gradient(circle at 50% 50%, #101210, #050605);
}
/* COATED GLASS: coating tint + specular crescent + catchlight. The three together
are what stop it reading as a coloured circle. */
.tb-51__glass {
background: radial-gradient(circle at 62% 70%, color-mix(in oklab, var(--color-tb51-coat) 55%, transparent), transparent 58%), radial-gradient(circle at 34% 26%, rgba(255,255,255,.5), transparent 42%), radial-gradient(circle at 50% 50%, var(--color-tb51-glass), #08181d 78%);
}
/* BLADE ANGLES: each blade carries its own --i inline; the rotation composes it.
rotate-[calc(var(--i)*60deg)] as a utility would compile ONE static rule that
all six blades share — the same trap as the shard button's angle. */
.tb-51__blade {
background: linear-gradient(120deg, var(--color-tb51-blade-hi), var(--color-tb51-blade));
transform-origin: 50% 50%;
rotate: calc(var(--i) * 60deg);
}
.tb-51 button:is(:hover, :focus-visible) .tb-51__blade {
rotate: calc(var(--i) * 60deg + 44deg);
}
/* Safari ≤15.3 shims for the two bezel ring masks. */
.tb-51__bezel > span:first-child {
-webkit-mask-image: radial-gradient(farthest-side, transparent 86%, #000 88%);
}
.tb-51__scale {
-webkit-mask-image: radial-gradient(farthest-side, transparent 82%, #000 84%);
}
@media (prefers-color-scheme: light) {
.tb-51:not([data-theme="dark"]) {
--color-tb51-bg: oklch(0.23 0.012 130);
}
}
.tb-51[data-theme="light"] {
--color-tb51-bg: oklch(0.23 0.012 130);
}
@media (forced-colors: active) {
.tb-51__bezel {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-51__bezel > span:first-child,
.tb-51__scale,
.tb-51__barrel > span:last-child {
display: none;
}
.tb-51__barrel {
background: Canvas;
box-shadow: inset 0 0 0 2px CanvasText;
}
.tb-51__blade {
background: ButtonText;
}
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Oswald:wght@500;600&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Oswald:wght@500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Utilities: the bezel, both masked rings, the barrel, all six blades' geometry and their scale-on-open, the flare sweep, the focusing rotation, the focus ring. CSS: the brass and glass gradients, and the two blade-rotation rules that compose the per-element
--i. - The blade rotation is the collection's closing lesson. A per-element variable inside an arbitrary utility compiles to one shared rule — every blade would sit at the same angle. The same trap appeared on the shard button (demo 20), and both times the answer is a two-line CSS rule plus an inline index. Utilities are per-class; this data is per-element.
- Six near-identical blade class lists is the honest cost, and it buys something real: each blade's open state (
group-hover:scale-50) is visible on the blade, which is where someone tuning the aperture will look. - A11y fix from the CSS original: the button's own
:focus-visiblesetoutline:noneand drew the ring on.bezel— correct — but the iris opened only on:hoverand:focus-visibleof the button, while the flare was hover-paired in the same selector. The port keeps them together and addsgroup-focus-visible:to both, so a keyboard user sees the full open-and-flare rather than a ring on a closed lens.
Here's a working CSS demo 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: Tailwind Brass Periscope Lens Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-brass-periscope-lens-button/
A brass periscope objective: a knurled bezel, an engraved focus scale, and a six-blade iris that opens on hover to reveal the glass — each blade a clipped triangle rotating about the optical centre, with a reflection sweep across the coated lens.
## HTML
```html
<div class="tb-51 w-full min-h-svh grid place-items-center font-tb51-sans p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" aria-label="Open the viewfinder" class="group cursor-pointer border-0 bg-transparent p-0 [-webkit-tap-highlight-color:transparent] focus-visible:outline-none disabled:cursor-not-allowed disabled:grayscale-[0.7] disabled:brightness-[0.85]">
<span aria-hidden="true" class="tb-51__bezel relative grid place-items-center size-38 rounded-full shadow-[inset_0_2px_3px_#ffffff73,inset_0_-3px_7px_#0000008c,0_18px_28px_-12px_#000000d9] transition-[rotate,filter] duration-[550ms] ease-tb51-out group-hover:brightness-105 group-active:rotate-6 group-focus-visible:outline-2 group-focus-visible:outline-offset-[5px] group-focus-visible:outline-tb51-brass-hi motion-reduce:transition-none">
<span class="absolute inset-0 rounded-full bg-[repeating-conic-gradient(from_0deg,#ffffff38_0_1.4deg,#00000047_1.4deg_3deg)] [mask-image:radial-gradient(farthest-side,transparent_86%,#000_88%)]"></span>
<span class="tb-51__scale absolute inset-[9%] rounded-full opacity-75 bg-[repeating-conic-gradient(from_-90deg,var(--color-tb51-brass-lo)_0_1deg,transparent_1deg_15deg)] [mask-image:radial-gradient(farthest-side,transparent_82%,#000_84%)]"></span>
<span class="tb-51__barrel relative grid place-items-center w-[66%] h-[66%] rounded-full overflow-hidden shadow-[inset_0_0_0_3px_var(--color-tb51-barrel-ring),inset_0_4px_10px_#000000e6]">
<span class="tb-51__glass absolute inset-0 rounded-full z-10"></span>
<span class="absolute inset-0 z-20 rounded-full">
<span style="--i:0" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:1" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:2" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:3" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:4" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:5" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
</span>
<span class="absolute -inset-[30%] z-30 pointer-events-none -translate-x-[70%] bg-[linear-gradient(104deg,transparent_42%,rgba(255,255,255,0.42)_50%,transparent_58%)] transition-transform duration-[900ms] ease-tb51-out group-hover:translate-x-[70%] group-focus-visible:translate-x-[70%] motion-reduce:hidden"></span>
</span>
</span>
</button>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb51-bg: #141613;
--color-tb51-bg: oklch(0.18 0.01 130);
--color-tb51-brass: #b28c4c;
--color-tb51-brass: oklch(0.66 0.08 84);
--color-tb51-brass-hi: #e8cc90;
--color-tb51-brass-hi: oklch(0.86 0.08 88);
--color-tb51-brass-lo: #6f5426;
--color-tb51-brass-lo: oklch(0.45 0.07 82);
--color-tb51-barrel-ring: #4a3819;
--color-tb51-barrel-ring: oklch(0.34 0.055 82);
--color-tb51-blade: #2a2b28;
--color-tb51-blade: oklch(0.28 0.004 130);
--color-tb51-blade-hi: #4a4b46;
--color-tb51-blade-hi: oklch(0.42 0.004 130);
--color-tb51-glass: #1a3b46;
--color-tb51-glass: oklch(0.32 0.05 220);
--color-tb51-coat: #4fd6c9;
--color-tb51-coat: oklch(0.82 0.12 182);
--font-tb51-sans: "Oswald", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb51-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-51 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(30rem 20rem at 50% 44%, color-mix(in oklab, var(--color-tb51-brass) 14%, transparent), transparent 74%), var(--color-tb51-bg);
}
.tb-51__bezel {
background: radial-gradient(circle at 34% 26%, var(--color-tb51-brass-hi), transparent 56%), linear-gradient(150deg, var(--color-tb51-brass), var(--color-tb51-brass-lo));
}
.tb-51__barrel {
background: radial-gradient(circle at 50% 50%, #101210, #050605);
}
/* COATED GLASS: coating tint + specular crescent + catchlight. The three together
are what stop it reading as a coloured circle. */
.tb-51__glass {
background: radial-gradient(circle at 62% 70%, color-mix(in oklab, var(--color-tb51-coat) 55%, transparent), transparent 58%), radial-gradient(circle at 34% 26%, rgba(255,255,255,.5), transparent 42%), radial-gradient(circle at 50% 50%, var(--color-tb51-glass), #08181d 78%);
}
/* BLADE ANGLES: each blade carries its own --i inline; the rotation composes it.
rotate-[calc(var(--i)*60deg)] as a utility would compile ONE static rule that
all six blades share — the same trap as the shard button's angle. */
.tb-51__blade {
background: linear-gradient(120deg, var(--color-tb51-blade-hi), var(--color-tb51-blade));
transform-origin: 50% 50%;
rotate: calc(var(--i) * 60deg);
}
.tb-51 button:is(:hover, :focus-visible) .tb-51__blade {
rotate: calc(var(--i) * 60deg + 44deg);
}
/* Safari ≤15.3 shims for the two bezel ring masks. */
.tb-51__bezel > span:first-child {
-webkit-mask-image: radial-gradient(farthest-side, transparent 86%, #000 88%);
}
.tb-51__scale {
-webkit-mask-image: radial-gradient(farthest-side, transparent 82%, #000 84%);
}
@media (prefers-color-scheme: light) {
.tb-51:not([data-theme="dark"]) {
--color-tb51-bg: oklch(0.23 0.012 130);
}
}
.tb-51[data-theme="light"] {
--color-tb51-bg: oklch(0.23 0.012 130);
}
@media (forced-colors: active) {
.tb-51__bezel {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-51__bezel > span:first-child,
.tb-51__scale,
.tb-51__barrel > span:last-child {
display: none;
}
.tb-51__barrel {
background: Canvas;
box-shadow: inset 0 0 0 2px CanvasText;
}
.tb-51__blade {
background: ButtonText;
}
}
```Here's a working CSS demo 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: Tailwind Brass Periscope Lens Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-brass-periscope-lens-button/
A brass periscope objective: a knurled bezel, an engraved focus scale, and a six-blade iris that opens on hover to reveal the glass — each blade a clipped triangle rotating about the optical centre, with a reflection sweep across the coated lens.
## HTML
```html
<div class="tb-51 w-full min-h-svh grid place-items-center font-tb51-sans p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" aria-label="Open the viewfinder" class="group cursor-pointer border-0 bg-transparent p-0 [-webkit-tap-highlight-color:transparent] focus-visible:outline-none disabled:cursor-not-allowed disabled:grayscale-[0.7] disabled:brightness-[0.85]">
<span aria-hidden="true" class="tb-51__bezel relative grid place-items-center size-38 rounded-full shadow-[inset_0_2px_3px_#ffffff73,inset_0_-3px_7px_#0000008c,0_18px_28px_-12px_#000000d9] transition-[rotate,filter] duration-[550ms] ease-tb51-out group-hover:brightness-105 group-active:rotate-6 group-focus-visible:outline-2 group-focus-visible:outline-offset-[5px] group-focus-visible:outline-tb51-brass-hi motion-reduce:transition-none">
<span class="absolute inset-0 rounded-full bg-[repeating-conic-gradient(from_0deg,#ffffff38_0_1.4deg,#00000047_1.4deg_3deg)] [mask-image:radial-gradient(farthest-side,transparent_86%,#000_88%)]"></span>
<span class="tb-51__scale absolute inset-[9%] rounded-full opacity-75 bg-[repeating-conic-gradient(from_-90deg,var(--color-tb51-brass-lo)_0_1deg,transparent_1deg_15deg)] [mask-image:radial-gradient(farthest-side,transparent_82%,#000_84%)]"></span>
<span class="tb-51__barrel relative grid place-items-center w-[66%] h-[66%] rounded-full overflow-hidden shadow-[inset_0_0_0_3px_var(--color-tb51-barrel-ring),inset_0_4px_10px_#000000e6]">
<span class="tb-51__glass absolute inset-0 rounded-full z-10"></span>
<span class="absolute inset-0 z-20 rounded-full">
<span style="--i:0" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:1" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:2" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:3" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:4" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
<span style="--i:5" class="tb-51__blade absolute inset-0 [clip-path:polygon(50%_50%,100%_6%,100%_94%)] shadow-[inset_0_0_0_1px_#00000080] transition-[rotate,scale,opacity] duration-[550ms] ease-tb51-out group-hover:scale-50 group-focus-visible:scale-50 motion-reduce:transition-none"></span>
</span>
<span class="absolute -inset-[30%] z-30 pointer-events-none -translate-x-[70%] bg-[linear-gradient(104deg,transparent_42%,rgba(255,255,255,0.42)_50%,transparent_58%)] transition-transform duration-[900ms] ease-tb51-out group-hover:translate-x-[70%] group-focus-visible:translate-x-[70%] motion-reduce:hidden"></span>
</span>
</span>
</button>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb51-bg: #141613;
--color-tb51-bg: oklch(0.18 0.01 130);
--color-tb51-brass: #b28c4c;
--color-tb51-brass: oklch(0.66 0.08 84);
--color-tb51-brass-hi: #e8cc90;
--color-tb51-brass-hi: oklch(0.86 0.08 88);
--color-tb51-brass-lo: #6f5426;
--color-tb51-brass-lo: oklch(0.45 0.07 82);
--color-tb51-barrel-ring: #4a3819;
--color-tb51-barrel-ring: oklch(0.34 0.055 82);
--color-tb51-blade: #2a2b28;
--color-tb51-blade: oklch(0.28 0.004 130);
--color-tb51-blade-hi: #4a4b46;
--color-tb51-blade-hi: oklch(0.42 0.004 130);
--color-tb51-glass: #1a3b46;
--color-tb51-glass: oklch(0.32 0.05 220);
--color-tb51-coat: #4fd6c9;
--color-tb51-coat: oklch(0.82 0.12 182);
--font-tb51-sans: "Oswald", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb51-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-51 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(30rem 20rem at 50% 44%, color-mix(in oklab, var(--color-tb51-brass) 14%, transparent), transparent 74%), var(--color-tb51-bg);
}
.tb-51__bezel {
background: radial-gradient(circle at 34% 26%, var(--color-tb51-brass-hi), transparent 56%), linear-gradient(150deg, var(--color-tb51-brass), var(--color-tb51-brass-lo));
}
.tb-51__barrel {
background: radial-gradient(circle at 50% 50%, #101210, #050605);
}
/* COATED GLASS: coating tint + specular crescent + catchlight. The three together
are what stop it reading as a coloured circle. */
.tb-51__glass {
background: radial-gradient(circle at 62% 70%, color-mix(in oklab, var(--color-tb51-coat) 55%, transparent), transparent 58%), radial-gradient(circle at 34% 26%, rgba(255,255,255,.5), transparent 42%), radial-gradient(circle at 50% 50%, var(--color-tb51-glass), #08181d 78%);
}
/* BLADE ANGLES: each blade carries its own --i inline; the rotation composes it.
rotate-[calc(var(--i)*60deg)] as a utility would compile ONE static rule that
all six blades share — the same trap as the shard button's angle. */
.tb-51__blade {
background: linear-gradient(120deg, var(--color-tb51-blade-hi), var(--color-tb51-blade));
transform-origin: 50% 50%;
rotate: calc(var(--i) * 60deg);
}
.tb-51 button:is(:hover, :focus-visible) .tb-51__blade {
rotate: calc(var(--i) * 60deg + 44deg);
}
/* Safari ≤15.3 shims for the two bezel ring masks. */
.tb-51__bezel > span:first-child {
-webkit-mask-image: radial-gradient(farthest-side, transparent 86%, #000 88%);
}
.tb-51__scale {
-webkit-mask-image: radial-gradient(farthest-side, transparent 82%, #000 84%);
}
@media (prefers-color-scheme: light) {
.tb-51:not([data-theme="dark"]) {
--color-tb51-bg: oklch(0.23 0.012 130);
}
}
.tb-51[data-theme="light"] {
--color-tb51-bg: oklch(0.23 0.012 130);
}
@media (forced-colors: active) {
.tb-51__bezel {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-51__bezel > span:first-child,
.tb-51__scale,
.tb-51__barrel > span:last-child {
display: none;
}
.tb-51__barrel {
background: Canvas;
box-shadow: inset 0 0 0 2px CanvasText;
}
.tb-51__blade {
background: ButtonText;
}
}
```How this works
Six identical blades, one index each. The element carries its position; the shared rule turns the index into an angle:
<span style="--i:3" class="… group-hover:scale-50"></span>
.tb-51__blade { rotate: calc(var(--i) * 60deg); }
.tb-51 button:is(:hover,:focus-visible) .tb-51__blade { rotate: calc(var(--i) * 60deg + 44deg); }The rotation stays CSS because it composes the per-element variable — rotate-[calc(var(--i)*60deg)] would compile one static rule shared by all six, which is the same trap as the shard button's angle.
The glass is three gradients: a broad coating tint, a hard specular crescent and a small catchlight. That combination is what reads as coated optical glass rather than a coloured circle.
The control is icon-only, so it carries an aria-label — the iris announces nothing.
Make it yours
- Blade count: update the
60degstep and the number of blades together (360 / n). - Open amount is the added rotation plus the blade
scale. - Coating tint: amber for a vintage uncoated lens, violet for modern multi-coating.
- Remove the focus-scale ring for a cleaner modern optic.
- The bezel's
group-active:rotate-6is the focusing gesture — drop it for a pure shutter.
Gotchas — read before shipping
- Six clipped blades transitioning at once is fine; sixty is not.
clip-pathon a rotated element clips in its own coordinate space — rotate the element, not the polygon.- Icon-only circular controls need an
aria-label. - Radial knurl masks alias below ~4rem; widen the ticks at small sizes.
- Keep the opening large enough at rest that users can tell what the control does.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
repeating-conic-gradient (Firefox 83) and individual rotate/scale utilities are both below Tailwind v4's own floor.