FORMS

Glass Contact Form

A modern glass contact form with an interactive reveal, floating labels, responsive styling and a smooth success state.

HTML
<div class="cr-popup-contact">

  <button
    class="cr-popup-contact__trigger"
    type="button"
    aria-expanded="false"
  >
    <span>Contact Us</span>
    <span
      class="cr-popup-contact__arrow"
      aria-hidden="true"
    >↗</span>
  </button>

  <div
    class="cr-popup-contact__panel"
    aria-hidden="true"
  >

    <div
      class="cr-popup-contact__card"
      role="dialog"
      aria-labelledby="cr-popup-title"
    >

      <span
        class="cr-popup-contact__glow cr-popup-contact__glow--purple"
        aria-hidden="true"
      ></span>

      <span
        class="cr-popup-contact__glow cr-popup-contact__glow--cyan"
        aria-hidden="true"
      ></span>

      <button
        class="cr-popup-contact__close"
        type="button"
        aria-label="Close contact form"
      >
        ×
      </button>

      <div class="cr-popup-contact__content">

        <span class="cr-popup-contact__eyebrow">
          GET IN TOUCH
        </span>

        <h2 id="cr-popup-title">
          Let's start a conversation
        </h2>

        <p class="cr-popup-contact__intro">
          Have a question or an idea? Send us a message and
          we'll get back to you.
        </p>

        <form class="cr-popup-contact__form">

          <div class="cr-popup-contact__field">

            <input
              id="cr-popup-name"
              type="text"
              name="name"
              placeholder=" "
              autocomplete="name"
              required
            >

            <label for="cr-popup-name">
              Your name
            </label>

          </div>

          <div class="cr-popup-contact__field">

            <input
              id="cr-popup-email"
              type="email"
              name="email"
              placeholder=" "
              autocomplete="email"
              required
            >

            <label for="cr-popup-email">
              Email address
            </label>

          </div>

          <div class="cr-popup-contact__field">

            <textarea
              id="cr-popup-message"
              name="message"
              rows="3"
              placeholder=" "
              required
            ></textarea>

            <label for="cr-popup-message">
              Your message
            </label>

          </div>

          <button
            class="cr-popup-contact__submit"
            type="submit"
          >
            <span>Send Message</span>
            <span aria-hidden="true">→</span>
          </button>

          <div
            class="cr-popup-contact__success"
            role="status"
            aria-live="polite"
          >

            <span class="cr-popup-contact__check">
              ✓
            </span>

            <div>
              <strong>Message sent!</strong>
              <p>Thanks for reaching out.</p>
            </div>

          </div>

        </form>

      </div>

    </div>

  </div>

</div>
CSS
.cr-popup-contact,
.cr-popup-contact * {
  box-sizing: border-box;
}

.cr-popup-contact {
  --cr-cyan: #22d3ee;
  --cr-purple: #8b5cf6;
  --cr-blue: #6366f1;
  --cr-text: #f8fafc;
  --cr-muted: #94a3b8;

  width: 100%;

  display: flex;
  flex-direction: column;
  align-items: center;

  padding: 40px 20px;

  font-family:
    Inter,
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif;
}


/* OPEN BUTTON */

.cr-popup-contact__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 11px;

  padding: 14px 20px;

  border: 1px solid rgba(34, 211, 238, 0.65);
  border-radius: 13px;

  color: #ffffff;

  font: inherit;
  font-size: 14px;
  font-weight: 700;

  cursor: pointer;

  background:
    linear-gradient(
      135deg,
      rgba(139, 92, 246, 0.18),
      rgba(34, 211, 238, 0.10)
    ),
    #0b1120;

  box-shadow:
    0 12px 30px rgba(0, 0, 0, 0.32),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);

  transition:
    transform 0.3s ease,
    border-color 0.3s ease,
    box-shadow 0.3s ease;
}

.cr-popup-contact__trigger:hover {
  transform: translateY(-3px);

  border-color: var(--cr-cyan);

  box-shadow:
    0 16px 35px rgba(0, 0, 0, 0.38),
    0 0 22px rgba(34, 211, 238, 0.14);
}

.cr-popup-contact__arrow {
  color: var(--cr-cyan);

  font-size: 18px;

  transition: transform 0.3s ease;
}

.cr-popup-contact__trigger:hover
.cr-popup-contact__arrow {
  transform: translate(3px, -3px);
}


/* PANEL */

.cr-popup-contact__panel {
  display: grid;

  width: 100%;

  grid-template-rows: 0fr;

  opacity: 0;
  visibility: hidden;

  transition:
    grid-template-rows 0.45s ease,
    opacity 0.3s ease,
    visibility 0.3s ease;
}

.cr-popup-contact__panel.is-open {
  grid-template-rows: 1fr;

  opacity: 1;
  visibility: visible;
}


/* CARD */

.cr-popup-contact__card {
  position: relative;

  width: min(100%, 460px);
  min-height: 0;

  margin: 0 auto;

  padding: 0 30px;

  overflow: hidden;

  color: var(--cr-text);

  border: 1px solid transparent;
  border-radius: 22px;

  background:
    linear-gradient(
      145deg,
      #111827,
      #070c1a
    );

  box-shadow:
    0 0 0 rgba(0, 0, 0, 0);

  transform:
    translateY(-10px)
    scale(0.98);

  transition:
    margin 0.4s ease,
    padding 0.4s ease,
    border-color 0.4s ease,
    box-shadow 0.4s ease,
    transform 0.4s ease;
}

.cr-popup-contact__panel:not(.is-open)
.cr-popup-contact__card {
  height: 0;
}

.cr-popup-contact__panel.is-open
.cr-popup-contact__card {
  height: auto;

  margin-top: 24px;

  padding: 30px;

  border-color: rgba(148, 163, 184, 0.18);

  box-shadow:
    0 28px 65px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);

  transform:
    translateY(0)
    scale(1);
}


/* GLOWS */

.cr-popup-contact__glow {
  position: absolute;

  border-radius: 50%;

  filter: blur(55px);

  opacity: 0.22;

  pointer-events: none;
}

.cr-popup-contact__glow--purple {
  width: 160px;
  height: 160px;

  top: -80px;
  right: -60px;

  background: var(--cr-purple);
}

.cr-popup-contact__glow--cyan {
  width: 140px;
  height: 140px;

  left: -70px;
  bottom: -60px;

  background: var(--cr-cyan);
}


/* CLOSE BUTTON */

.cr-popup-contact__close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 5;

  width: 34px;
  height: 34px;

  display: grid;
  place-items: center;

  padding: 0;

  border: 1px solid rgba(148, 163, 184, 0.20);
  border-radius: 10px;

  color: #cbd5e1;

  font: inherit;
  font-size: 22px;
  line-height: 1;

  cursor: pointer;

  background: rgba(255, 255, 255, 0.04);

  transition:
    transform 0.25s ease,
    color 0.25s ease,
    background 0.25s ease,
    border-color 0.25s ease;
}

.cr-popup-contact__close:hover {
  color: #ffffff;

  border-color: rgba(34, 211, 238, 0.45);

  background: rgba(34, 211, 238, 0.08);

  transform: rotate(90deg);
}


/* CONTENT */

.cr-popup-contact__content {
  position: relative;
  z-index: 2;
}

.cr-popup-contact__eyebrow {
  display: inline-block;

  margin-bottom: 9px;

  color: var(--cr-cyan);

  font-size: 10px;
  font-weight: 800;

  letter-spacing: 1.6px;
}

.cr-popup-contact__content h2 {
  max-width: 340px;

  margin: 0 0 9px;

  padding-right: 30px;

  color: #ffffff;

  font-size: 29px;
  font-weight: 800;
  line-height: 1.12;

  letter-spacing: -0.5px;
}

.cr-popup-contact__intro {
  max-width: 370px;

  margin: 0 0 20px;

  color: var(--cr-muted);

  font-size: 13px;
  line-height: 1.6;
}


/* FORM */

.cr-popup-contact__form {
  display: grid;

  gap: 12px;
}

.cr-popup-contact__field {
  position: relative;
}

.cr-popup-contact__field input,
.cr-popup-contact__field textarea {
  display: block;

  width: 100%;

  margin: 0;

  border: 1px solid rgba(148, 163, 184, 0.18);
  border-radius: 11px;

  outline: none;

  color: #ffffff;

  font: inherit;
  font-size: 13px;

  background: rgba(3, 8, 20, 0.62);

  transition:
    border-color 0.25s ease,
    background 0.25s ease,
    box-shadow 0.25s ease;
}

.cr-popup-contact__field input {
  height: 50px;

  padding:
    19px
    14px
    6px;
}

.cr-popup-contact__field textarea {
  height: 82px;

  padding:
    20px
    14px
    8px;

  resize: none;
}


/* FLOATING LABELS */

.cr-popup-contact__field label {
  position: absolute;

  top: 15px;
  left: 14px;

  color: #7f8ba0;

  font-size: 13px;

  pointer-events: none;

  transition:
    top 0.2s ease,
    color 0.2s ease,
    font-size 0.2s ease;
}

.cr-popup-contact__field input:focus + label,
.cr-popup-contact__field input:not(:placeholder-shown) + label,
.cr-popup-contact__field textarea:focus + label,
.cr-popup-contact__field textarea:not(:placeholder-shown) + label {
  top: 6px;

  color: var(--cr-cyan);

  font-size: 9px;
  font-weight: 700;
}

.cr-popup-contact__field input:focus,
.cr-popup-contact__field textarea:focus {
  border-color: rgba(34, 211, 238, 0.68);

  background: rgba(4, 12, 28, 0.82);

  box-shadow:
    0 0 0 3px rgba(34, 211, 238, 0.06),
    0 0 18px rgba(34, 211, 238, 0.05);
}


/* SEND BUTTON */

.cr-popup-contact__submit {
  width: 100%;
  height: 48px;

  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;

  border: 0;
  border-radius: 11px;

  color: #ffffff;

  font: inherit;
  font-size: 13px;
  font-weight: 800;

  cursor: pointer;

  background:
    linear-gradient(
      120deg,
      #7c3aed,
      #6366f1 50%,
      #06b6d4
    );

  box-shadow:
    0 11px 25px rgba(99, 102, 241, 0.24);

  transition:
    transform 0.3s ease,
    box-shadow 0.3s ease;
}

.cr-popup-contact__submit:hover {
  transform: translateY(-2px);

  box-shadow:
    0 15px 30px rgba(99, 102, 241, 0.32);
}


/* SUCCESS */

.cr-popup-contact__success {
  display: none;

  align-items: center;
  gap: 10px;

  padding: 10px 12px;

  border: 1px solid rgba(34, 211, 238, 0.25);
  border-radius: 11px;

  background: rgba(34, 211, 238, 0.07);
}

.cr-popup-contact__success.is-visible {
  display: flex;
}

.cr-popup-contact__check {
  flex: 0 0 auto;

  width: 30px;
  height: 30px;

  display: grid;
  place-items: center;

  border-radius: 50%;

  color: #031014;

  font-size: 15px;
  font-weight: 900;

  background: var(--cr-cyan);
}

.cr-popup-contact__success strong {
  display: block;

  color: #ffffff;

  font-size: 12px;
}

.cr-popup-contact__success p {
  margin: 1px 0 0;

  color: var(--cr-muted);

  font-size: 11px;
}


/* FOCUS */

.cr-popup-contact__trigger:focus-visible,
.cr-popup-contact__close:focus-visible,
.cr-popup-contact__submit:focus-visible {
  outline: 3px solid rgba(34, 211, 238, 0.65);
  outline-offset: 3px;
}


/* MOBILE */

@media (max-width: 600px) {

  .cr-popup-contact {
    padding: 30px 12px;
  }

  .cr-popup-contact__panel.is-open
  .cr-popup-contact__card {
    padding:
      26px
      17px
      18px;
  }

  .cr-popup-contact__content h2 {
    font-size: 24px;
  }

  .cr-popup-contact__intro {
    margin-bottom: 16px;
  }

  .cr-popup-contact__field input {
    height: 48px;
  }

  .cr-popup-contact__field textarea {
    height: 74px;
  }

  .cr-popup-contact__submit {
    height: 46px;
  }
}


/* REDUCED MOTION */

@media (prefers-reduced-motion: reduce) {

  .cr-popup-contact__trigger,
  .cr-popup-contact__arrow,
  .cr-popup-contact__panel,
  .cr-popup-contact__card,
  .cr-popup-contact__close,
  .cr-popup-contact__field input,
  .cr-popup-contact__field textarea,
  .cr-popup-contact__field label,
  .cr-popup-contact__submit {
    transition: none;
  }
}
JavaScript
(() => {
  const components = document.querySelectorAll(
    ".cr-popup-contact"
  );

  components.forEach((component) => {
    if (component.dataset.ready === "true") return;

    component.dataset.ready = "true";

    const trigger = component.querySelector(
      ".cr-popup-contact__trigger"
    );

    const panel = component.querySelector(
      ".cr-popup-contact__panel"
    );

    const closeButton = component.querySelector(
      ".cr-popup-contact__close"
    );

    const form = component.querySelector(
      ".cr-popup-contact__form"
    );

    const success = component.querySelector(
      ".cr-popup-contact__success"
    );

    if (!trigger || !panel || !closeButton) return;


    const openCard = () => {
      trigger.style.display = "none";

      trigger.setAttribute(
        "aria-expanded",
        "true"
      );

      panel.classList.add("is-open");

      panel.setAttribute(
        "aria-hidden",
        "false"
      );
    };


    const closeCard = () => {
      panel.classList.remove("is-open");

      panel.setAttribute(
        "aria-hidden",
        "true"
      );

      trigger.setAttribute(
        "aria-expanded",
        "false"
      );

      trigger.style.display = "inline-flex";
    };


    trigger.addEventListener(
      "click",
      openCard
    );


    closeButton.addEventListener(
      "click",
      closeCard
    );


    document.addEventListener(
      "keydown",
      (event) => {
        if (
          event.key === "Escape" &&
          panel.classList.contains("is-open")
        ) {
          closeCard();
        }
      }
    );


    if (form) {
      form.addEventListener(
        "submit",
        (event) => {
          event.preventDefault();

          if (!form.checkValidity()) {
            form.reportValidity();
            return;
          }

          form.reset();

          if (success) {
            success.classList.add(
              "is-visible"
            );

            setTimeout(() => {
              success.classList.remove(
                "is-visible"
              );
            }, 3000);
          }
        }
      );
    }
  });
})();

How This Glass Contact Form Works

The Glass Contact Form is a collapsible contact interface built with HTML, CSS, and JavaScript. It starts with a compact “Contact Us” trigger button. When the visitor clicks it, JavaScript hides the trigger and opens a dark glass-style contact card containing name, email, and message fields.

The HTML separates the component into a trigger, expandable panel, dialog-style card, form fields, submit button, and success message. The card uses role="dialog" with aria-labelledby, while the trigger maintains an aria-expanded state. JavaScript also updates the panel’s aria-hidden value when the form opens or closes.

The opening animation is primarily controlled through .is-open. The panel begins with grid-template-rows: 0fr, zero opacity, and hidden visibility. When opened, it changes to 1fr, becomes visible, and fades in. The card simultaneously moves from translateY(-10px) scale(0.98) to its normal position and scale. These transitions run for approximately 0.3s to 0.45s. For more details about this animation technique, see the MDN CSS transitions documentation.

The card is limited to 460px wide and uses a dark gradient from #111827 to #070c1a. A subtle border, inset highlight, and large shadow create its glass-like visual treatment. Two absolutely positioned blurred circles provide purple and cyan background glows. Both use filter: blur(55px) and low opacity so they remain behind the form content rather than competing with it.

Each field uses a floating-label pattern. The inputs have a blank placeholder=" ", allowing the CSS :placeholder-shown state to determine whether a field contains content. When an input or textarea is focused or no longer showing its placeholder, its label moves from top: 15px to 6px, shrinks from 13px to 9px, and changes to cyan. Focus also gives the field a cyan border and subtle glow.

JavaScript handles the component’s interactive state. It finds every .cr-popup-contact instance and marks initialized components with data-ready="true" to prevent duplicate setup. Clicking the trigger calls openCard(), while the close button calls closeCard(). Pressing the Escape key also closes an open panel.

Form submission is simulated on the front end. The submit handler calls preventDefault(), checks native HTML validity with checkValidity(), and uses reportValidity() when required fields are missing or invalid. After a valid submission, it resets the form and displays the success message for three seconds with setTimeout(). The code does not send the message to a server or external service.

Customizing This Glass Contact Form

The component centralizes several visual choices in CSS variables and clearly separated styles, making its appearance straightforward to adjust.

  • Change --cr-cyan, --cr-purple, and --cr-blue to adapt the accent palette.
  • Adjust the card’s 460px maximum width or 22px border radius for a different form shape.
  • Modify the #111827 and #070c1a card gradient colors to change the dark background.
  • Edit the 160px and 140px glow dimensions, 55px blur, or 0.22 opacity to control the background lighting.
  • Change the input 50px height and textarea 82px height to make the fields more compact or spacious.
  • Adjust the submit button’s purple, indigo, and cyan gradient colors.
  • Change the panel and card transition durations between 0.3s and 0.45s to control opening speed.
  • Edit the 3000 millisecond setTimeout() value in JavaScript to keep the success confirmation visible for a different duration.

Where to Use This Glass Contact Form

The expandable design works particularly well when a page needs a contact option without displaying a full form all the time.

  • Portfolio websites for client enquiries
  • Agency landing pages
  • Freelancer service pages
  • SaaS product pages
  • Small business contact sections
  • Product enquiry interfaces
  • Developer portfolio projects
  • Dark-themed website layouts

The current submit behavior is a front-end demonstration, so a production version still needs a backend or form-handling service to actually deliver messages. For a more conventional always-visible layout, the related Contact Form component offers another approach.

Frequently Asked Questions

Does the Glass Contact Form actually send messages?

No. The current JavaScript prevents normal submission, validates the fields, resets the form, and displays a success notification. It does not contain an API request or backend integration for sending the submitted data.

Can users close the contact form with the keyboard?

Yes. A document-level keydown listener checks for the Escape key and closes the panel when it currently has the is-open class. The interactive buttons also include visible focus styling for keyboard navigation.

How do the floating labels work?

Each input and textarea has a blank placeholder. CSS combines :focus and :not(:placeholder-shown) selectors to move the corresponding label upward when the field receives focus or contains a value.

Is the Glass Contact Form responsive?

Yes. At screen widths of 600px or less, the outer padding is reduced, the card uses narrower horizontal padding, the heading drops to 24px, and the inputs, textarea, and submit button become slightly shorter.

The Glass Contact Form combines an expandable panel, floating labels, responsive styling, keyboard controls, native validation, and temporary submission feedback in a compact contact interface.

Scroll to Top