How to Create a Contact Form in HTML

This recipe builds a contact form with name and email inputs plus a resizable message textarea, including a live character counter and layout that adapts from a single column to a two-column row on wider screens.

What Goes Into a Contact Form

A contact form is the simplest of the three, but the textarea makes it worth its own recipe: it needs sizing rules, a sensible character limit, and — because messages are usually the whole point of the form — a way to let people see how much room they have left as they type.

  • Name — type="text" with autocomplete="name"
  • Email — type="email", so replies have somewhere to go
  • Message — a <textarea>, not an <input>, since it needs to hold multiple lines
  • A submit button that's clearly labeled with the action, e.g. "Send Message" rather than just "Submit"

Example

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Contact Us</title>
<style>
  * { box-sizing: border-box; }
  body {
    font-family: system-ui, sans-serif;
    background: #f3f4f6;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    margin: 0;
    padding: 1rem;
  }
  .contact-card {
    background: #fff;
    padding: 2rem;
    border-radius: 12px;
    box-shadow: 0 10px 25px rgba(0,0,0,0.08);
    width: 100%;
    max-width: 480px;
  }
  .contact-card h1 {
    font-size: 1.5rem;
    margin: 0 0 1.5rem;
    text-align: center;
  }
  .field {
    margin-bottom: 1.25rem;
  }
  .field label {
    display: block;
    font-size: 0.875rem;
    font-weight: 600;
    margin-bottom: 0.375rem;
  }
  .field input,
  .field textarea {
    width: 100%;
    padding: 0.625rem 0.75rem;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    font-size: 1rem;
    font-family: inherit;
  }
  .field textarea {
    resize: vertical;
    min-height: 120px;
  }
  .char-count {
    font-size: 0.75rem;
    color: #6b7280;
    text-align: right;
    margin-top: 0.25rem;
  }
  .submit-btn {
    width: 100%;
    padding: 0.75rem;
    background: #2563eb;
    color: #fff;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
  }
</style>
</head>
<body>
  <form class="contact-card" action="/contact" method="post">
    <h1>Get in Touch</h1>
    <div class="field">
      <label for="contact-name">Name</label>
      <input type="text" id="contact-name" name="name" autocomplete="name" required>
    </div>
    <div class="field">
      <label for="contact-email">Email</label>
      <input type="email" id="contact-email" name="email" autocomplete="email" required>
    </div>
    <div class="field">
      <label for="contact-message">Message</label>
      <textarea id="contact-message" name="message" rows="5" maxlength="500" required></textarea>
      <div class="char-count"><span id="count">0</span>/500</div>
    </div>
    <button type="submit" class="submit-btn">Send Message</button>
  </form>

  <script>
    const message = document.getElementById('contact-message');
    const count = document.getElementById('count');

    message.addEventListener('input', () => {
      count.textContent = message.value.length;
    });
  </script>
</body>
</html>

The textarea uses rows="5" to set a starting height and resize: vertical in CSS so people can drag it taller if they're writing something long, but can't drag it wider and break the card's layout. maxlength="500" caps the length the browser will accept, which pairs with the live counter so nobody hits the limit by surprise.

Note: Prefer resize: vertical over resize: both (or leaving the browser default) on a textarea inside a fixed-width card. Horizontal resizing lets the box grow past its container and overlap or break your layout on narrow screens.

Making the Layout Responsive

On a narrow screen, every field should stack in one column so nothing gets cramped. On a wider screen, it's common to let the name and email fields sit side by side in a row, since they're both short single-line inputs, while the message textarea still spans the full width underneath.

Example

<!DOCTYPE html>
<html>
<head>
<style>
.contact-card {
  display: block;
}

.field {
  margin-bottom: 1.25rem;
}

/* Stack fields on narrow screens, but let name + email
   sit side by side once there is enough room. */
@media (min-width: 480px) {
  .contact-card .name-email-row {
    display: flex;
    gap: 1rem;
  }
  .contact-card .name-email-row .field {
    flex: 1;
  }
}

.field textarea:focus,
.field input:focus {
  outline: 2px solid #2563eb;
  outline-offset: 1px;
  border-color: #2563eb;
}

.field textarea:invalid:not(:placeholder-shown) {
  border-color: #dc2626;
}
</style>
</head>
<body>

<form class="contact-card">
  <div class="name-email-row">
    <div class="field">
      <label for="contact-name">Name</label>
      <input type="text" id="contact-name" name="name" required>
    </div>
    <div class="field">
      <label for="contact-email">Email</label>
      <input type="email" id="contact-email" name="email" required>
    </div>
  </div>
  <div class="field">
    <label for="contact-message">Message</label>
    <textarea id="contact-message" name="message" rows="5" required></textarea>
  </div>
</form>

</body>
</html>
ElementWhy this choice
<textarea> instead of <input>Only <textarea> supports multiple lines and lets people see several sentences at once while writing
maxlength="500" + JS counterThe attribute enforces the limit; the counter tells the person about it before they hit it
resize: verticalLets people expand the box for a long message without breaking the surrounding layout
autocomplete="name" / "email"Lets returning visitors' browsers fill in their own details instantly
Note: Client-side required attributes and length limits improve the experience, but they don't stop a bot or a scripted request from POSTing directly to your endpoint. Treat every field as untrusted on the server: re-validate length and format, and consider a honeypot field or rate limiting if spam becomes a problem.

Once the layout and validation feel right, the contact form pairs naturally with the login and signup recipes: the same card styling, spacing, and focus states can be reused across all three so the whole site's forms feel like one consistent system.

Exercise: Forms

What does the required attribute on an input enable without any JavaScript?

Frequently Asked Questions

How do I create a contact form in HTML?
Use a <form> with name and email inputs and a <textarea> for the message. Add required to the fields you need, and give every control a <label> so the form is usable with a screen reader.
How do I make a contact form actually send email?
HTML alone cannot send mail. Point the form's action at a backend endpoint or a form service, which receives the submission and sends the message. The mailto: action exists but opens the visitor's mail client and is unreliable.