Home/Components/Popup Models/Responsive Modal Popup Form Tailwind CSS Javascript
Back to search
Code · Live Preview
Componenthtml
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Modal Form — Formfield</title>

    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:wght@300;400;500;600&display=swap"
      rel="stylesheet"
    />

    <script src="https://cdn.tailwindcss.com"></script>
    <script>
      tailwind.config = {
        theme: {
          extend: {
            fontFamily: {
              serif: ["DM Serif Display", "serif"],
              sans: ["DM Sans", "sans-serif"],
            },
            colors: {
              ink: "#111110",
              "ink-light": "#1e1d1b",
              "off-white": "#f5f4f1",
              "warm-muted": "#888580",
              "warm-border": "#2a2927",
              "warm-mid": "#e8e7e4",
              "warm-bg": "#f5f4f1",
              err: "#c0392b",
              "err-bg": "#fdf2f0",
              ok: "#2d6a4f",
              "ok-bg": "#f0faf5",
            },
            boxShadow: {
              focus: "0 0 0 3px rgba(17,17,16,0.07)",
              "focus-err": "0 0 0 3px rgba(192,57,43,0.09)",
              "focus-ok": "0 0 0 3px rgba(45,106,79,0.09)",
              modal:
                "0 8px 40px rgba(17,17,16,0.12), 0 2px 8px rgba(17,17,16,0.06)",
            },
            keyframes: {
              spin: { to: { transform: "rotate(360deg)" } },
            },
            animation: {
              spin: "spin 0.7s linear infinite",
            },
          },
        },
      };
    </script>

    <style>
      /* Unavoidable: modal open/close transition (transform + opacity together) */
      #modal-panel {
        transition:
          opacity 0.25s ease,
          transform 0.28s cubic-bezier(0.34, 1.3, 0.64, 1);
      }
      #modal-overlay {
        transition: opacity 0.25s ease;
      }
      /* Strength bar width transition */
      #strength-fill {
        transition:
          width 0.3s ease,
          background-color 0.3s ease;
      }
      /* Custom checkbox checked mark — Tailwind can't do ::after pseudo */
      .form-checkbox:checked::after {
        content: "";
        position: absolute;
        top: 2px;
        left: 5px;
        width: 4px;
        height: 7px;
        border: 1.5px solid #f5f4f1;
        border-top: none;
        border-left: none;
        transform: rotate(45deg);
      }
      /* Scrollbar */
      #modal-panel::-webkit-scrollbar {
        width: 4px;
      }
      #modal-panel::-webkit-scrollbar-track {
        background: transparent;
      }
      #modal-panel::-webkit-scrollbar-thumb {
        background: #e8e7e4;
        border-radius: 4px;
      }
      #modal-panel {
        scrollbar-width: thin;
        scrollbar-color: #e8e7e4 transparent;
      }
    </style>
  </head>
  <body class="font-sans bg-warm-bg text-ink min-h-screen">
    <!-- NAVBAR -->
    <nav
      class="sticky top-0 z-[100] border-b border-warm-mid"
      style="background: rgba(245, 244, 241, 0.92); backdrop-filter: blur(12px)"
    >
      <div class="max-w-[1280px] mx-auto px-6 lg:px-10">
        <div class="flex items-center justify-between h-16">
          <a href="#" class="flex items-center gap-2 no-underline">
            <div
              class="w-7 h-7 bg-ink flex items-center justify-center rounded-[3px]"
            >
              <span class="font-serif text-[14px] text-off-white leading-none"
                >F</span
              >
            </div>
            <span
              class="font-serif text-[19px] text-ink tracking-tight leading-none"
            >
              Formfield<span class="text-warm-muted">.</span>
            </span>
          </a>

          <span
            class="hidden sm:block text-[10px] font-medium uppercase tracking-[1.8px] text-warm-muted"
          >
            Components
          </span>

          <div></div>
        </div>
      </div>
    </nav>

    <!-- TRIGGER PAGE -->
    <main
      class="min-h-[calc(100vh-64px)] flex flex-col items-center justify-center px-4 py-16"
    >
      <!-- Card -->
      <div
        class="bg-white border border-warm-mid rounded-[6px] p-10 sm:p-12 text-center w-full max-w-[440px]"
      >
        <p
          class="text-[10px] font-medium uppercase tracking-[1.8px] text-warm-muted mb-3"
        >
          Modal Component
        </p>
        <h1
          class="font-serif text-[1.8rem] text-ink leading-tight mb-3"
          style="letter-spacing: -0.02em"
        >
          Get in Touch
        </h1>
        <p
          class="text-[14px] text-warm-muted leading-relaxed mb-8 max-w-[260px] mx-auto"
        >
          Fill out the form and we'll get back to you within 24 hours.
        </p>

        <div class="flex flex-col gap-3">
          <button
            id="open-modal"
            class="w-full flex items-center justify-center gap-2 px-5 py-[11px] bg-ink text-off-white text-[14px] font-medium rounded-[4px] border-none cursor-pointer hover:bg-ink-light transition-colors duration-150"
            aria-haspopup="dialog"
          >
            Open Contact Form
            <span
              id="btn-arrow-trigger"
              class="inline-block transition-transform duration-150"
              >→</span
            >
          </button>

          <button
            id="open-modal-2"
            class="w-full flex items-center justify-center gap-2 px-5 py-[11px] bg-transparent text-warm-muted text-[14px] font-normal rounded-[4px] border border-warm-mid cursor-pointer hover:text-ink hover:border-warm-border hover:bg-warm-bg transition-colors duration-150"
            aria-haspopup="dialog"
          >
            Send us a message
          </button>
        </div>

        <p class="text-[11px] text-warm-muted mt-6 uppercase tracking-[1.4px]">
          Press
          <kbd
            class="bg-warm-bg border border-warm-mid rounded-[3px] px-1.5 py-px text-[10px] font-sans normal-case tracking-normal"
            >Esc</kbd
          >
          to close
        </p>
      </div>

      <!-- Feature pills -->
      <div class="mt-8 flex items-center gap-5 flex-wrap justify-center">
        <div class="flex items-center gap-2">
          <div class="w-1.5 h-1.5 rounded-full bg-warm-muted"></div>
          <span class="text-[11px] uppercase tracking-[1.4px] text-warm-muted"
            >Real-time validation</span
          >
        </div>
        <div class="flex items-center gap-2">
          <div class="w-1.5 h-1.5 rounded-full bg-warm-muted"></div>
          <span class="text-[11px] uppercase tracking-[1.4px] text-warm-muted"
            >Focus trap</span
          >
        </div>
        <div class="flex items-center gap-2">
          <div class="w-1.5 h-1.5 rounded-full bg-warm-muted"></div>
          <span class="text-[11px] uppercase tracking-[1.4px] text-warm-muted"
            >Accessible</span
          >
        </div>
      </div>
    </main>

    <!-- OVERLAY -->
    <div
      id="modal-overlay"
      class="fixed inset-0 z-[200] opacity-0 pointer-events-none"
      style="background: rgba(17, 17, 16, 0.45); backdrop-filter: blur(4px)"
      role="presentation"
    ></div>

    <!-- MODAL PANEL -->
    <div
      id="modal-panel"
      class="fixed top-1/2 left-1/2 z-[300] w-full max-w-[480px] max-sm:max-w-[calc(100vw-32px)] bg-white border border-warm-mid rounded-[6px] opacity-0 pointer-events-none overflow-y-auto max-h-[90vh] px-6 max-sm:px-5"
      style="
        transform: translate(-50%, -46%);
        box-shadow:
          0 8px 40px rgba(17, 17, 16, 0.12),
          0 2px 8px rgba(17, 17, 16, 0.06);
      "
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      tabindex="-1"
    >
      <!-- Header -->
      <div
        class="flex items-center justify-between py-5 sticky top-0 bg-white z-10 border-b border-warm-mid"
      >
        <div>
          <p
            class="text-[10px] font-medium uppercase tracking-[1.8px] text-warm-muted mb-1"
          >
            Contact Form
          </p>
          <h2
            id="modal-title"
            class="font-serif text-[1.3rem] text-ink leading-none"
            style="letter-spacing: -0.01em"
          >
            Send a Message
          </h2>
        </div>
        <button
          id="close-modal"
          class="w-8 h-8 flex items-center justify-center rounded-[4px] border border-warm-mid bg-transparent cursor-pointer hover:border-warm-border hover:bg-warm-bg transition-colors duration-150 flex-shrink-0"
          aria-label="Close modal"
        >
          <svg width="11" height="11" viewBox="0 0 11 11" fill="none">
            <path
              d="M1 1l9 9M10 1L1 10"
              stroke="#888580"
              stroke-width="1.5"
              stroke-linecap="round"
            />
          </svg>
        </button>
      </div>

      <!-- Success state -->
      <div
        id="success-state"
        class="hidden flex-col items-center justify-center text-center py-12 gap-4"
      >
        <div
          class="w-12 h-12 border border-warm-mid rounded-[4px] flex items-center justify-center bg-ok-bg"
        >
          <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
            <path
              d="M4 11l5 5L18 6"
              stroke="#2d6a4f"
              stroke-width="1.8"
              stroke-linecap="round"
              stroke-linejoin="round"
            />
          </svg>
        </div>
        <div>
          <h3
            class="font-serif text-[1.4rem] text-ink mb-2"
            style="letter-spacing: -0.01em"
          >
            Message sent
          </h3>
          <p class="text-[13px] text-warm-muted leading-relaxed">
            Thanks for reaching out. We'll get back to you within 24 hours.
          </p>
        </div>
        <button
          onclick="resetForm()"
          class="mt-2 flex items-center gap-2 px-5 py-[9px] bg-transparent text-warm-muted text-[13px] font-normal rounded-[4px] border border-warm-mid cursor-pointer hover:text-ink hover:border-warm-border hover:bg-warm-bg transition-colors duration-150"
        >
          Send another
        </button>
      </div>

      <!-- Form -->
      <form id="form-body" novalidate autocomplete="off">
        <div class="flex flex-col gap-5 py-6">
          <!-- Name + Email grid -->
          <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
            <!-- Full Name -->
            <div class="flex flex-col">
              <label
                class="flex items-center gap-0.5 text-[13px] font-medium text-ink mb-1.5 font-sans"
                for="field-name"
              >
                Full Name
                <span class="text-err text-[13px] ml-0.5" aria-hidden="true"
                  >*</span
                >
              </label>
              <input
                id="field-name"
                type="text"
                placeholder="Alex Johnson"
                autocomplete="name"
                aria-required="true"
                aria-describedby="name-msg"
                class="h-[42px] w-full bg-warm-bg border border-warm-mid rounded-[4px] text-[14px] text-ink px-3.5 placeholder:text-warm-muted font-sans outline-none transition-all duration-150 focus:border-ink focus:bg-white focus:shadow-focus"
              />
              <p
                id="name-msg"
                class="mt-1.5 text-[12px] text-warm-muted font-sans min-h-[16px]"
                role="alert"
                aria-live="polite"
              >
                Your first and last name
              </p>
            </div>

            <!-- Email -->
            <div class="flex flex-col">
              <label
                class="flex items-center gap-0.5 text-[13px] font-medium text-ink mb-1.5 font-sans"
                for="field-email"
              >
                Email Address
                <span class="text-err text-[13px] ml-0.5" aria-hidden="true"
                  >*</span
                >
              </label>
              <input
                id="field-email"
                type="email"
                placeholder="alex@studio.co"
                autocomplete="email"
                aria-required="true"
                aria-describedby="email-msg"
                class="h-[42px] w-full bg-warm-bg border border-warm-mid rounded-[4px] text-[14px] text-ink px-3.5 placeholder:text-warm-muted font-sans outline-none transition-all duration-150 focus:border-ink focus:bg-white focus:shadow-focus"
              />
              <p
                id="email-msg"
                class="mt-1.5 text-[12px] text-warm-muted font-sans min-h-[16px]"
                role="alert"
                aria-live="polite"
              >
                We'll never share your email
              </p>
            </div>
          </div>

          <!-- Password -->
          <div class="flex flex-col">
            <label
              class="flex items-center gap-0.5 text-[13px] font-medium text-ink mb-1.5 font-sans"
              for="field-password"
            >
              Password
              <span class="text-err text-[13px] ml-0.5" aria-hidden="true"
                >*</span
              >
            </label>
            <div class="relative">
              <input
                id="field-password"
                type="password"
                placeholder="Create a password"
                autocomplete="new-password"
                aria-required="true"
                aria-describedby="password-msg"
                class="h-[42px] w-full bg-warm-bg border border-warm-mid rounded-[4px] text-[14px] text-ink pl-3.5 pr-10 placeholder:text-warm-muted font-sans outline-none transition-all duration-150 focus:border-ink focus:bg-white focus:shadow-focus"
              />
              <button
                type="button"
                id="pw-toggle-btn"
                aria-label="Show password"
                class="absolute right-3 top-1/2 -translate-y-1/2 bg-transparent border-none cursor-pointer text-warm-muted hover:text-ink transition-colors duration-150 p-0.5 leading-none"
              >
                <svg
                  id="eye-icon"
                  width="16"
                  height="16"
                  viewBox="0 0 16 16"
                  fill="none"
                >
                  <ellipse
                    cx="8"
                    cy="8"
                    rx="6.5"
                    ry="4"
                    stroke="currentColor"
                    stroke-width="1.2"
                  />
                  <circle
                    cx="8"
                    cy="8"
                    r="2"
                    stroke="currentColor"
                    stroke-width="1.2"
                  />
                </svg>
                <svg
                  id="eye-off-icon"
                  width="16"
                  height="16"
                  viewBox="0 0 16 16"
                  fill="none"
                  class="hidden"
                >
                  <path
                    d="M2 2l12 12"
                    stroke="currentColor"
                    stroke-width="1.2"
                    stroke-linecap="round"
                  />
                  <path
                    d="M6.5 5.5A3.5 3.5 0 0 1 8 5c3 0 5.5 3 5.5 3s-.7 1.2-2 2.2"
                    stroke="currentColor"
                    stroke-width="1.2"
                    stroke-linecap="round"
                  />
                  <path
                    d="M9.5 10.5A3.5 3.5 0 0 1 8 11c-3 0-5.5-3-5.5-3s.7-1.2 2-2.2"
                    stroke="currentColor"
                    stroke-width="1.2"
                    stroke-linecap="round"
                  />
                </svg>
              </button>
            </div>
            <!-- Strength bar -->
            <div
              class="mt-2 h-[3px] w-full bg-warm-mid rounded-full overflow-hidden"
            >
              <div id="strength-fill" class="h-full rounded-full w-0"></div>
            </div>
            <p
              id="password-msg"
              class="mt-1.5 text-[12px] text-warm-muted font-sans min-h-[16px]"
              role="alert"
              aria-live="polite"
            >
              Minimum 8 characters
            </p>
          </div>

          <!-- Phone (optional) -->
          <div class="flex flex-col">
            <label
              class="flex items-center gap-1.5 text-[13px] font-medium text-ink mb-1.5 font-sans"
              for="field-phone"
            >
              Phone
              <span class="text-[11px] font-normal text-warm-muted"
                >(optional)</span
              >
            </label>
            <input
              id="field-phone"
              type="tel"
              placeholder="+1 (555) 000-0000"
              autocomplete="tel"
              aria-describedby="phone-msg"
              class="h-[42px] w-full bg-warm-bg border border-warm-mid rounded-[4px] text-[14px] text-ink px-3.5 placeholder:text-warm-muted font-sans outline-none transition-all duration-150 focus:border-ink focus:bg-white focus:shadow-focus"
            />
            <p
              id="phone-msg"
              class="mt-1.5 text-[12px] text-warm-muted font-sans min-h-[16px]"
              role="alert"
              aria-live="polite"
            >
              Include country code
            </p>
          </div>

          <!-- Message -->
          <div class="flex flex-col">
            <label
              class="flex items-center gap-0.5 text-[13px] font-medium text-ink mb-1.5 font-sans"
              for="field-message"
            >
              Message
              <span class="text-err text-[13px] ml-0.5" aria-hidden="true"
                >*</span
              >
            </label>
            <textarea
              id="field-message"
              placeholder="Tell us about your project, timeline, and budget…"
              maxlength="500"
              aria-required="true"
              aria-describedby="message-msg"
              class="w-full min-h-[96px] resize-y bg-warm-bg border border-warm-mid rounded-[4px] text-[14px] text-ink px-3.5 py-3 placeholder:text-warm-muted font-sans leading-relaxed outline-none transition-all duration-150 focus:border-ink focus:bg-white focus:shadow-focus"
            ></textarea>
            <div class="flex items-start justify-between mt-1.5">
              <p
                id="message-msg"
                class="text-[12px] text-warm-muted font-sans min-h-[16px]"
                role="alert"
                aria-live="polite"
              >
                Minimum 20 characters
              </p>
              <span
                id="char-counter"
                class="text-[11px] text-warm-muted font-sans flex-shrink-0"
              >
                0 / 500
              </span>
            </div>
          </div>

          <!-- Divider -->
          <div class="h-px bg-warm-mid w-full"></div>

          <!-- Terms -->
          <div class="flex items-start gap-3">
            <div class="relative flex-shrink-0 mt-px">
              <input
                type="checkbox"
                id="field-terms"
                aria-required="true"
                aria-describedby="terms-msg"
                class="form-checkbox w-4 h-4 appearance-none border border-warm-mid rounded-[3px] bg-warm-bg cursor-pointer transition-all duration-150 checked:bg-ink checked:border-ink focus:outline-none focus:shadow-focus relative"
              />
            </div>
            <div>
              <label
                for="field-terms"
                class="text-[13px] text-warm-muted leading-relaxed cursor-pointer hover:text-ink transition-colors duration-150 font-sans"
              >
                I agree to the
                <a
                  href="#"
                  class="text-ink underline underline-offset-[2px] hover:text-warm-muted transition-colors duration-150"
                  >Terms of Service</a
                >
                and
                <a
                  href="#"
                  class="text-ink underline underline-offset-[2px] hover:text-warm-muted transition-colors duration-150"
                  >Privacy Policy</a
                >
              </label>
              <p
                id="terms-msg"
                class="hidden mt-1 text-[12px] text-err font-sans"
                role="alert"
                aria-live="polite"
              ></p>
            </div>
          </div>
        </div>

        <!-- Footer -->
        <div class="flex flex-col gap-3 pb-6 pt-5 border-t border-warm-mid">
          <!-- Submit -->
          <button
            type="submit"
            id="submit-btn"
            class="w-full flex items-center justify-center gap-2 px-5 py-[11px] bg-ink text-off-white text-[14px] font-medium rounded-[4px] border-none cursor-pointer hover:bg-ink-light transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed"
          >
            <span id="btn-label">Send Message</span>
            <span
              id="btn-arrow"
              class="inline-block transition-transform duration-150"
              >→</span
            >
            <span
              id="btn-spinner"
              class="hidden w-4 h-4 border-2 rounded-full animate-spin flex-shrink-0"
              style="
                border-color: rgba(245, 244, 241, 0.3);
                border-top-color: #f5f4f1;
              "
            >
            </span>
          </button>

          <!-- Cancel -->
          <button
            type="button"
            id="cancel-btn"
            class="w-full flex items-center justify-center gap-2 px-5 py-[11px] bg-transparent text-warm-muted text-[14px] font-normal rounded-[4px] border border-warm-mid cursor-pointer hover:text-ink hover:border-warm-border hover:bg-warm-bg transition-colors duration-150"
          >
            Cancel
          </button>

          <!-- Status note -->
          <p
            class="text-[11px] text-center text-warm-muted uppercase tracking-[1.4px] font-sans"
          >
            We respond within 24 hours
          </p>
        </div>
      </form>
    </div>

    <!-- Link JavaScript -->
    <script src="app.js"></script>
  </body>
</html>
Detecting libraries...

Component details

Overview, install command, dependencies, and project links.

A centered modal popup with a fully validated contact form — built with HTML, Tailwind CSS, and vanilla JavaScript. No frameworks, no libraries, just clean utility-first code. The modal opens from a trigger button, animates in with a spring transition, and traps focus inside for full keyboard accessibility. The form has five fields: full name, email, password, phone (optional), and a message textarea. Every required field validates on blur first, then switches to live input validation so it doesn't yell at you before you've finished typing. Password has a show/hide toggle and a 5-level strength bar that shifts from red to green. The textarea tracks characters live with a counter that warns at 450/500. On submit it validates everything at once, focuses the first error automatically, shows a loading spinner, and transitions to a success state after the async call. Resetting brings everything cleanly back to zero. Tailwind handles all styling — layout, color, states, responsive, transitions. Custom CSS is kept to exactly 4 things that Tailwind genuinely cannot do: the modal's dual-easing open/close transition, the strength bar width + color transition, the checkbox checkmark pseudo-element, and scrollbar styling.

Added Apr 25, 2026

Discussion

Feedback, implementation tips, and usage notes.

0 threads

Comments

Sign in to join the conversation