📝 Html Css

HTML Form Validation

P
Author
Pyland
📅
Published
30.06.2026
⏱️
Reading time
2 min
👁️
Views
84
🌿
Level
Medium

Browsers can validate form data before it’s sent to the server — without a single line of JavaScript. Built-in validation speeds up feedback for the user, but it never replaces server-side checks.

The required attribute

Prevents a form from being submitted with an empty field:

<input type="text" name="username" required />
<textarea name="message" required></textarea>
<select name="category" required>
  <option value="">-- Choose --</option>
  <option value="python">Python</option>
</select>

Trying to submit with an empty required field shows a browser tooltip.

Length and range constraints

<!-- Text length -->
<input
  type="text"
  name="title"
  minlength="5"
  maxlength="100"
  required
/>

<!-- Number range -->
<input
  type="number"
  name="age"
  min="18"
  max="120"
  step="1"
  required
/>

<!-- Date range -->
<input
  type="date"
  name="publish_date"
  min="2024-01-01"
  max="2030-12-31"
/>
Attribute Applies to Description
minlength text, email, password, textarea Minimum number of characters
maxlength same Maximum number of characters
min number, date, time Minimum value
max same Maximum value
step number, date Step between allowed values

Input type as a validator

Some <input> types automatically validate the format:

<!-- Checks for @ and a domain dot -->
<input type="email" name="email" required />

<!-- Checks URL format -->
<input type="url" name="website" placeholder="https://example.com" />

<!-- Phone — format varies by browser, not reliable alone -->
<input type="tel" name="phone" />

Pattern matching

A regular expression for precise format validation:

<!-- Only Latin letters and digits, 3–20 characters -->
<input
  type="text"
  name="username"
  pattern="[a-zA-Z0-9]{3,20}"
  title="Latin letters and digits only, 3 to 20 characters"
  required
/>

<!-- Phone number format -->
<input
  type="tel"
  name="phone"
  pattern="\+1\d{10}"
  placeholder="+12025550100"
  title="Format: +1XXXXXXXXXX"
/>

<!-- URL-friendly post slug -->
<input
  type="text"
  name="slug"
  pattern="[a-z0-9-]+"
  title="Lowercase letters, digits, and hyphens only"
/>

The title attribute holds the hint text the browser shows on error.

The :valid and :invalid pseudo-classes

CSS can style fields based on their validation state:

/* Default state — neutral border */
input {
  border: 1px solid #d1d5db;
  border-radius: 4px;
  padding: 0.5rem;
  outline: none;
  transition: border-color 0.2s;
}

/* Field in focus */
input:focus {
  border-color: #3b82f6;
}

/* Valid field (after user interaction) */
input:valid {
  border-color: #22c55e;
}

/* Invalid field */
input:invalid {
  border-color: #ef4444;
}

Problem: :invalid fires immediately on page load for all required fields, before the user has typed anything. The fix is to use :user-invalid (modern browsers) or only activate styles after the first submit attempt:

/* Fires only after user interaction — supported in Chrome 119+ */
input:user-invalid {
  border-color: #ef4444;
}

Or add a class to the form on submit via JavaScript:

<style>
  .was-validated input:invalid {
    border-color: #ef4444;
  }
</style>

<form id="contact-form">
  <input type="email" name="email" required />
  <button type="submit">Send</button>
</form>

<script>
  document.getElementById("contact-form").addEventListener("submit", (e) => {
    e.currentTarget.classList.add("was-validated");
  });
</script>

Disabling built-in validation

Sometimes you need fully custom validation logic. The novalidate attribute on the form disables browser validation:

<form action="/contact/" method="post" novalidate>
  <!-- Validation handled by JavaScript or Django -->
</form>

Where responsibility lies

Built-in HTML validation solves the UX problem: fast feedback before a request is sent. But it’s trivially bypassed — via DevTools, curl, or any HTTP client.

Server-side validation is always required:

# Django validates form fields automatically
class ContactForm(forms.Form):
    name = forms.CharField(min_length=2, max_length=100)
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea, min_length=10)

def contact(request):
    form = ContactForm(request.POST or None)
    if form.is_valid():
        # data is clean — process it
        pass
    return render(request, "contact.html", {"form": form})

Rule: HTML validation is for user convenience. Server-side validation is for data security. Neither alone is sufficient.

Your reaction to the article

💬 Comments (0)

🔐 Sign in to leave a comment
🚪 Login
💭

No comments yet

Be the first to share your opinion about this article!

🔗 Similar

Similar articles

Continue learning with these materials

📝

What is an ORM

ORM (Object-Relational Mapping) is a technology that lets you work with a database through Python...

📅 30.06.2026 👁️ 131
📝

AI Agents: ReAct Loop and Autonomous Actions

A chatbot answers questions. An agent takes action: it calls tools, retrieves real data, and...

📅 30.06.2026 👁️ 100
📝

RAG: Chatting with Documents via Vector Search

RAG (Retrieval-Augmented Generation) is a pattern for working with your own documents. Instead of fine-tuning...

📅 30.06.2026 👁️ 93

Did you like the article?

Subscribe to our updates and receive new articles first. Grow with PyLand!