Dark Glassmorphism UI System
Design tokens (@theme)
Define all colors, fonts, and spacing in globals.css using Tailwind v4 @theme:
@import "tailwindcss";
@theme {
/* Navy scale — darkest to lightest */
--color-navy-950: #060d1b;
--color-navy-900: #0a1628;
--color-navy-800: #0f1d32;
--color-navy-700: #162744;
--color-navy-600: #1e3358;
/* Accent (warm orange) */
--color-accent: #f97316;
--color-accent-light: #fb923c;
/* Cyber (cool teal) */
--color-cyber: #06b6d4;
--color-cyber-light: #22d3ee;
/* Typography */
--font-sans: var(--font-inter, "Inter"), ui-sans-serif, system-ui, sans-serif;
--font-display: var(--font-space-grotesk, "Space Grotesk"), ui-sans-serif, system-ui, sans-serif;
}
Body background: bg-navy-950. Primary text: text-white or text-gray-200. Secondary text: text-gray-400.
Card glass
The signature component — frosted translucent panels:
.card-glass {
background: rgba(15, 29, 50, 0.55);
backdrop-filter: blur(20px) saturate(1.3);
-webkit-backdrop-filter: blur(20px) saturate(1.3);
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow:
inset 0 1px 0 0 rgba(255, 255, 255, 0.04),
0 4px 24px rgba(0, 0, 0, 0.25),
0 1px 2px rgba(0, 0, 0, 0.15);
position: relative;
}
.card-glass::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: url("data:image/svg+xml,...") repeat; /* subtle noise texture */
opacity: 0.03;
pointer-events: none;
}
.card-glass:hover {
border-color: rgba(249, 115, 22, 0.15);
box-shadow:
inset 0 1px 0 0 rgba(255, 255, 255, 0.06),
0 4px 32px rgba(0, 0, 0, 0.3),
0 0 0 1px rgba(249, 115, 22, 0.08);
}
Usage: <div className="card-glass rounded-2xl p-6">...</div>
Button system
Global baseline (in globals.css)
@layer base {
button, [role="button"] {
transition-property: transform, opacity, filter, box-shadow,
background-color, border-color, color;
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
cursor: pointer;
}
button:disabled, [role="button"][aria-disabled="true"] {
cursor: not-allowed;
}
}
button:active:not(:disabled),
[role="button"]:active:not(:disabled) {
transform: scale(0.975);
}
button:focus-visible,
[role="button"]:focus-visible {
outline: 2px solid rgba(249, 115, 22, 0.5);
outline-offset: 2px;
}
This eliminates the need for per-button transition, active:scale-*, and focus:outline-* classes.
Primary CTA button
<button className="btn-shimmer rounded-xl bg-gradient-to-r from-accent to-amber-500
px-8 py-3 font-bold text-white shadow-xl shadow-accent/20
hover:shadow-accent/40 hover:scale-[1.02] disabled:opacity-35">
Action
</button>
Shimmer effect
.btn-shimmer {
position: relative;
overflow: hidden;
}
.btn-shimmer::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
105deg,
transparent 40%,
rgba(255, 255, 255, 0.15) 45%,
rgba(255, 255, 255, 0.15) 55%,
transparent 60%
);
animation: shimmer 2.5s ease-in-out infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
Secondary / Ghost button
<button className="rounded-xl border border-white/10 bg-white/5 px-6 py-2.5
text-sm text-gray-300 hover:bg-white/10 hover:text-white
disabled:opacity-35">
Secondary
</button>
Ambient background
Two-layer approach for cinematic depth without harming readability:
- Canvas starfield — static stars with subtle twinkling and mouse parallax
- CSS orbs — soft radial-gradient blobs with slow CSS animation
// Starfield: <canvas> with requestAnimationFrame
// Orbs: absolute-positioned divs with radial-gradient + CSS animation
// Both layers sit behind content with pointer-events: none
Keep orb colors dim (opacity 0.08–0.15) and blur heavy (blur(80px)+) to avoid UI interference.
Noise overlay
Adds film grain texture across the entire page:
.noise {
position: fixed;
inset: 0;
z-index: 50;
pointer-events: none;
opacity: 0.015;
background: url("data:image/svg+xml,...") repeat;
background-size: 200px;
}
Accessibility baseline
Reduced motion
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Required ARIA patterns
- Progress indicators:
role="progressbar"+aria-valuenow/min/max - Interactive non-button elements:
role="button"+tabindex="0"+ keyboard handler - Modals:
role="dialog"+aria-modal="true"+ focus trap
Color contrast
- Body text on navy-950: use
text-gray-200minimum (ratio > 7:1) - Secondary text:
text-gray-400(ratio > 4.5:1) - Never rely solely on color to convey information
Checklist
-
@themetokens defined — never use raw hex in components -
.card-glasswith backdrop-filter, noise pseudo-element, hover state - Global button baseline in
@layer base(transition, active, focus-visible) -
.btn-shimmerfor primary CTAs - Noise overlay at
opacity: 0.015,pointer-events: none -
prefers-reduced-motionmedia query in globals.css - All interactive elements keyboard-accessible with visible focus