EmpireUI
Get Pro
← Blog8 min read#glassmorphism#signup#auth

Glassmorphism Sign-Up Page: Frosted Form on Blurred Gradient Background

Build a glassmorphism sign-up page with a frosted-glass form card floating over a blurred gradient background — pure CSS and React, no libraries needed.

frosted glass signup form floating over colorful blurred gradient background

Why a Glassmorphism Sign-Up Page Actually Works

Auth pages are the most underrated real estate in any app. Your users land here first, judge you instantly, and decide whether they trust you enough to hand over their email — all before they've seen a single feature. A generic white card on a grey background screams "Bootstrap 3." Glassmorphism does the opposite: it signals craft without screaming for attention.

The effect itself is simple. You've got a semi-transparent card with a backdrop-filter: blur() sitting over a vivid, out-of-focus gradient background. The card doesn't fight the background — it floats above it, catching the color. Done well, it feels expensive. Done badly, it just looks like frosted glass stuck to a wall. The difference is in the details, specifically the background opacity, the blur radius, and the border treatment.

Honestly, glassmorphism on a sign-up page is one of the best use cases for the style. It's a contained layout — one card, a few inputs, a button. There's no navigation clutter or complex data hierarchy to worry about. That gives you full creative room to let the background do the visual work while the form stays readable and functional.

Worth noting: this approach also keeps your page fast. No hero video, no massive image. Just a CSS gradient and a blur filter. You'll hit a 100 Lighthouse performance score without trying. Browse the glassmorphism components on Empire UI to see the card patterns this article builds on.

Setting Up the Background: Gradient + Blur Done Right

The background is doing most of the visual lifting here, so don't rush it. You want a radial or multi-stop linear gradient with at least two contrasting hues. Purple-to-cyan is the classic move, but indigo-to-rose-to-amber reads great in 2026 and feels less overused. Whatever you pick, make it saturated — the blur washes out color, so if you start muted, you'll end muddy.

Your full-page wrapper needs position: relative, overflow: hidden, and a fixed min-height. The gradient goes directly on the wrapper. Then you add a few absolutely positioned blurred circle blobs using pseudo-elements or extra divs. These blobs are just circles with a filter: blur(80px) and a semi-opaque color — they give the background depth without you needing an image. The 80px value is key; go below 60px and the blobs start to feel hard-edged, go above 120px and everything melts into a single flat color.

.signup-bg {
  position: relative;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
  overflow: hidden;
}

.signup-bg::before {
  content: '';
  position: absolute;
  width: 600px;
  height: 600px;
  border-radius: 50%;
  background: rgba(130, 80, 255, 0.45);
  filter: blur(80px);
  top: -150px;
  left: -100px;
}

.signup-bg::after {
  content: '';
  position: absolute;
  width: 500px;
  height: 500px;
  border-radius: 50%;
  background: rgba(0, 200, 255, 0.35);
  filter: blur(100px);
  bottom: -100px;
  right: -80px;
}

Quick aside: Safari on macOS still has occasional backdrop-filter compositing bugs as of early 2026 when you use overflow: hidden on a parent and apply blur on a child. The safest fix is to give the blurred element its own stacking context with will-change: transform and transform: translateZ(0) — not translate3d, the shorthand form works better cross-browser.

If you want a visual shortcut to nail the gradient before writing CSS, the gradient generator on Empire UI lets you copy the exact CSS output. Faster than eyeballing hex values.

Building the Frosted Glass Card

The card is where you'd normally reach for a UI kit, but you really don't need one. Four CSS properties do the job: background, backdrop-filter, border, and box-shadow. The background should be white at somewhere between 8% and 15% opacity — rgba(255, 255, 255, 0.10) is a solid default. Too high and the card turns solid; too low and it disappears against a bright gradient.

.glass-card {
  position: relative;
  z-index: 10;
  width: 100%;
  max-width: 420px;
  padding: 40px;
  border-radius: 20px;
  background: rgba(255, 255, 255, 0.10);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow:
    0 8px 32px rgba(0, 0, 0, 0.37),
    inset 0 1px 0 rgba(255, 255, 255, 0.15);
}

The inset 0 1px 0 rgba(255,255,255,0.15) on the box-shadow is the detail most people skip. It simulates a top-edge highlight — like light catching the rim of physical frosted glass. You won't consciously notice it, but you definitely notice when it's absent. The card looks flatter without it.

In practice, the blur radius on backdrop-filter should match what's behind the card. If your blobs are blurred at 80px already, a card blur of 20px is plenty — the combined effect feels right. Crank the card blur above 30px and you start losing too much of the color bleed from the background, which is the whole point of the style.

You can also reference the glassmorphism generator to tweak these values interactively before committing to code. It previews the exact blur, opacity, and border combos in real-time.

React Component: Full Sign-Up Form

Let's build the full thing as a React component. No dependencies beyond what React gives you. We'll use uncontrolled inputs with useRef for simplicity, but this drops straight into React Hook Form if you need validation without changing the markup.

import { useRef, FormEvent } from 'react';
import styles from './SignupPage.module.css';

export default function SignupPage() {
  const emailRef = useRef<HTMLInputElement>(null);
  const passwordRef = useRef<HTMLInputElement>(null);
  const nameRef = useRef<HTMLInputElement>(null);

  function handleSubmit(e: FormEvent) {
    e.preventDefault();
    const data = {
      name: nameRef.current?.value,
      email: emailRef.current?.value,
      password: passwordRef.current?.value,
    };
    console.log('Signup data:', data);
  }

  return (
    <div className={styles.bg}>
      <form className={styles.card} onSubmit={handleSubmit}>
        <h1 className={styles.title}>Create account</h1>
        <p className={styles.subtitle}>Free forever. No credit card.</p>

        <div className={styles.field}>
          <label htmlFor="name">Full name</label>
          <input
            id="name"
            type="text"
            placeholder="Ada Lovelace"
            ref={nameRef}
            autoComplete="name"
            required
          />
        </div>

        <div className={styles.field}>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            type="email"
            placeholder="ada@example.com"
            ref={emailRef}
            autoComplete="email"
            required
          />
        </div>

        <div className={styles.field}>
          <label htmlFor="password">Password</label>
          <input
            id="password"
            type="password"
            placeholder="••••••••"
            ref={passwordRef}
            autoComplete="new-password"
            minLength={8}
            required
          />
        </div>

        <button type="submit" className={styles.btn}>
          Get started
        </button>

        <p className={styles.signin}>
          Already have an account? <a href="/login">Sign in</a>
        </p>
      </form>
    </div>
  );
}

The CSS module keeps things scoped. Here's the full SignupPage.module.css to match:

.bg {
  position: relative;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
  overflow: hidden;
}

.bg::before {
  content: '';
  position: absolute;
  width: 600px;
  height: 600px;
  border-radius: 50%;
  background: rgba(130, 80, 255, 0.45);
  filter: blur(80px);
  top: -150px;
  left: -100px;
  pointer-events: none;
}

.bg::after {
  content: '';
  position: absolute;
  width: 500px;
  height: 500px;
  border-radius: 50%;
  background: rgba(0, 200, 255, 0.35);
  filter: blur(100px);
  bottom: -100px;
  right: -80px;
  pointer-events: none;
}

.card {
  position: relative;
  z-index: 10;
  width: 100%;
  max-width: 420px;
  padding: 40px;
  border-radius: 20px;
  background: rgba(255, 255, 255, 0.10);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow:
    0 8px 32px rgba(0, 0, 0, 0.37),
    inset 0 1px 0 rgba(255, 255, 255, 0.15);
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.title {
  color: #fff;
  font-size: 1.75rem;
  font-weight: 700;
  margin: 0;
}

.subtitle {
  color: rgba(255, 255, 255, 0.55);
  font-size: 0.9rem;
  margin: -12px 0 0;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.field label {
  color: rgba(255, 255, 255, 0.75);
  font-size: 0.85rem;
  font-weight: 500;
}

.field input {
  padding: 12px 16px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  background: rgba(255, 255, 255, 0.07);
  color: #fff;
  font-size: 0.95rem;
  outline: none;
  transition: border-color 0.2s;
}

.field input::placeholder {
  color: rgba(255, 255, 255, 0.3);
}

.field input:focus {
  border-color: rgba(130, 80, 255, 0.7);
  background: rgba(255, 255, 255, 0.12);
}

.btn {
  padding: 14px;
  border-radius: 10px;
  border: none;
  background: linear-gradient(135deg, #7c3aed, #2563eb);
  color: #fff;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: opacity 0.2s, transform 0.1s;
}

.btn:hover {
  opacity: 0.9;
}

.btn:active {
  transform: scale(0.98);
}

.signin {
  text-align: center;
  color: rgba(255, 255, 255, 0.5);
  font-size: 0.85rem;
}

.signin a {
  color: rgba(130, 80, 255, 0.9);
  text-decoration: none;
}

.signin a:hover {
  text-decoration: underline;
}

Look, the inputs are themselves mini glass panels. Each one gets the same background: rgba(255,255,255,0.07) treatment as the card, just dialed back further. This is the layered transparency approach — each UI level sits slightly more opaque than the one behind it, creating depth without needing actual 3D transforms.

Input Focus States and Accessibility

One thing people get wrong with glassmorphism forms is killing accessibility chasing aesthetics. Your inputs need visible focus indicators. The default browser outline is ugly, yes, but replacing it with outline: none and nothing else is a WCAG 2.1 failure. The border-color transition in the CSS above handles this — at focus, the border shifts to a semi-opaque purple. That's enough contrast on a dark background to pass AA.

Color contrast is the other landmine. White text on a 10% white background over a dark gradient reads fine visually, but your labels are at 75% opacity — rgba(255,255,255,0.75). Run that through a contrast checker against your average background value. On the dark purple gradient we're using, it clears 4.5:1. If you shift to a lighter gradient (say, pastel pink to pale yellow), you'd need to rethink your text colors entirely.

What about users who have prefers-reduced-motion set? The animations here are subtle — a color transition and a scale transform — so you probably won't get complaints. Still, it's worth wrapping motion-heavy additions (if you add floating animations or shimmer effects) in a media query: ``css @media (prefers-reduced-motion: reduce) { .card, .field input, .btn { transition: none; } } ``

One more thing — make sure your <label> elements are properly associated with their inputs via htmlFor / id pairs. The markup above does this correctly. Screen readers on frosted-glass forms work exactly like any other form; the visual treatment is CSS-only and doesn't touch semantics.

Tailwind CSS Version: Same Effect, Less Code

If you're on a Tailwind project, you don't need CSS modules at all. The Tailwind v3.3+ utilities cover backdrop-blur, bg-white/10, and border-white/20 directly. Your card becomes a single class string:

<form className="relative z-10 w-full max-w-md p-10 rounded-2xl bg-white/10 backdrop-blur-xl border border-white/20 shadow-[0_8px_32px_rgba(0,0,0,0.37)] flex flex-col gap-5">
  {/* fields */}
</form>

The background blobs need a little custom CSS still — Tailwind doesn't have a built-in filter: blur(80px) utility that applies to non-backdrop elements. You can drop them in a globals.css file or add them as arbitrary values with [filter:blur(80px)] if you're comfortable with that pattern. Either works.

That said, if you're generating lots of glassmorphism components across an app, a shared design token approach scales better than per-component arbitrary values. Define your glass variables once in :root--glass-bg, --glass-border, --glass-blur — and reference them everywhere. It also makes theming between light and dark modes a single variable swap rather than a full class audit. See how Empire UI handles this across its glassmorphism components for a production-ready reference.

For quick iteration on blur values and opacities without touching code, the glassmorphism generator gives you live sliders and exports the Tailwind class string or raw CSS directly.

Polish: Micro-Details That Separate Good from Great

The baseline component above looks solid. Here's what takes it from 'looks nice' to 'who built this?' — three small additions. First, a shimmer border on the card. Instead of a static border: 1px solid rgba(255,255,255,0.18), you animate a conic gradient along the border. It's a CSS-only trick using a pseudo-element and @keyframes. Budget about 20 lines of CSS.

@keyframes border-spin {
  to { --angle: 360deg; }
}

@property --angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

.card-shimmer {
  border: 1px solid transparent;
  background-clip: padding-box;
  position: relative;
}

.card-shimmer::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  background: conic-gradient(
    from var(--angle),
    transparent 70%,
    rgba(130, 80, 255, 0.6) 80%,
    rgba(0, 200, 255, 0.6) 90%,
    transparent 100%
  );
  animation: border-spin 4s linear infinite;
  z-index: -1;
}

This requires @property support, which landed in Firefox 128 and has been in Chrome since 111. Safari added it in version 17.2. You're at roughly 92% global browser coverage in 2026, so it's safe to use without a fallback for most projects.

Second addition: a floating label animation on focus. When the user clicks into an input, the label slides up and shrinks from 14px to 11px. It's a UX pattern borrowed from Material Design but it works especially well on glass forms where space is visually tight. Third: a success state on the submit button — replace the gradient with a green pulse on form submission. None of these are mandatory. But they're the details users describe when they say 'the sign-up felt really smooth.'

FAQ

Does backdrop-filter work in all browsers?

backdrop-filter has been supported in Chrome since version 76, Safari since 9 (with -webkit- prefix), and Firefox since 103. You're at 96%+ global coverage in 2026. Add -webkit-backdrop-filter as a fallback and you're done — no polyfill needed.

How do I make the glass card look right on mobile?

Scale the max-width to 100% with 16px horizontal padding on small screens, and reduce the blob sizes in your pseudo-elements to around 300px or they'll overflow and cause horizontal scroll. The blur and opacity values don't need to change — they look the same at any screen size.

Can I use glassmorphism with dark mode?

Yes, and it's actually easier than light mode. Dark glassmorphism keeps the same rgba(255,255,255,0.10) card background — the dark gradient underneath does the theming work. For light mode you'd switch to a lighter gradient and a darker card tint like rgba(0,0,0,0.08) instead.

What's the minimum blur value that still reads as glassmorphism?

Anything below 8px stops looking frosted and starts looking like low opacity. 12px is the practical floor for the effect to register. 16–24px is the sweet spot for form cards — readable, glassy, without making the background completely unrecognizable behind the card.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Glassmorphism Form Design: Login, Signup and Contact FormsGlassmorphism Navbar: Floating Frosted-Glass Navigation in ReactOTP Input in React: 6-Digit Code Entry With Auto-Focus and PasteNewsletter Signup in React: Form, Success State, Email Validation