32

I'm building a form and I want to use the :invalid selector to give the "required" input fields a red border if the user presses submit without filling them, but using this makes them appear highlighted right when the page loads. It seems unfriendly to give this kind of warning to the user before even giving him the chance to fill them at least once.

Is there a way that these fields appear highlighted only after trying to submit the form, said in another way, is there a way to run the validation only after clicking submit (or at least losing focus on the required input fields?)

rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156
Enrique Moreno Tent
  • 24,127
  • 34
  • 104
  • 189
  • use the `:invalid` pseudo-class for users w/o JavaScript support, and override it with *real* classes for users with JS support. – zzzzBov Sep 28 '11 at 00:44

15 Answers15

10

I used this approach for a project of mine, so the invalid fields would be highlighted only after submit:

HTML:

<form>
  <input type="email" required placeholder="Email Address">
  <input type="password" required placeholder="Password">
  <input type="submit" value="Sign in">
</form>

CSS:

input.required:invalid {
    color: red;
}

JS (jQuery):

$('[type="submit"]').on('click', function () {
    // this adds 'required' class to all the required inputs under the same <form> as the submit button
    $(this)
        .closest('form')
        .find('[required]')
        .addClass('required');
});
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Cezar D.
  • 356
  • 1
  • 6
  • 12
8

Very simple just use #ID:invalid:focus

This only does the validation when focused on and not on page load

conner monty
  • 169
  • 2
  • 7
7

In addition to @Alexander Farkas' post, Dave Rupert has a very workable solution here: Happier HTML5 Form Validation.

Essentially, what it does is add a CSS class to form input elements that only displays after a user attempts to submit the form. This is much better UX, in that these elements won't show the invalid styling by default, or when a user tabs through them, which enhances accessibility.

Prior to finding this, I tried styling elements with :invalid:focus and other pseudo-elements, but didn't get the desired effect. Although I try to do my styling with pure CSS as much as possible, this looks like a use case where efficient JS is the practical solution.

karolus
  • 912
  • 7
  • 13
5

No there is nothing out of the box.

Mozilla has its own pseudoclass for something very similiar called ':-moz-ui-invalid'. If you want to achieve something like this, you have to use the constraint validation DOM-API:

if(document.addEventListener){
    document.addEventListener('invalid', function(e){
        e.target.className += ' invalid';
    }, true);
}

You can also use webshims lib polyfill, which will not only polyfill incapable browsers, but also adds something similiar like -moz-ui-invalid to all browser (.form-ui-invalid).

alexander farkas
  • 13,754
  • 4
  • 40
  • 41
  • Oh well, if there isnt anything you can do, I might as well do JS validation :P But that sucks :P – Enrique Moreno Tent Sep 27 '11 at 23:20
  • 1
    @alexander farkas the marking remains after the user enters information to a field. How can I remove the className, let's say, onBlur ? – JonSnow Apr 08 '19 at 14:46
  • This cannot work: the `invalid` event is triggered from a form element and not `document`, check https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/invalid_event – tanguy_k Feb 07 '23 at 21:19
3

Another way is to add a hide-hints class to the inputs with JavaScript on load. When a user modifies a field you remove the class.

In your CSS you then apply styling to input:not(.hide-hints):invalid. This means the error styling will be shown for users without JavaScript as well.

Jacob Rask
  • 22,804
  • 8
  • 38
  • 36
2

Here's another solution that only styles the inputs after a submit attempt.

HTML

<form id="sign-up-form" method="post" enctype="multipart/form-data" class="login-form sign-in-form">
    <input type="text" placeholder="Name" id="full-name" name="full-name" required>
    ...
</form>
<script>
    (() => {
        const form = document.getElementById('sign-up-form');
        form.noValidate = true;

        form.onsubmit = function(e) {
            e.preventDefault();
            this.reportValidity();

            if (this.checkValidity()) return this.submit();

            this.classList.add('submit-attempt')
        }
    })()
</script>

CSS

.submit-attempt input:invalid, .submit-attempt select:invalid {
    border: 1px solid red;
}
mpen
  • 272,448
  • 266
  • 850
  • 1,236
1

Old question, but for people that might might find it useful: I made a little script that adds a class to a form when it's attempted to be submitted, so that you can style forms that have and haven't been attempted to be submitted differently:

window.addEventListener('load', function() {

    /**
     * Adds a class `_submit-attempted` to a form when it's attempted to be
     * submitted.
     *
     * This allows us to style invalid form fields differently for forms that
     * have and haven't been attemted to submit.
     */
    function addFormSubmitAttemptedTriggers() {

        var formEls = document.querySelectorAll('form');

        for (var i = 0; i < formEls.length; i++) {

            function addSubmitAttemptedTrigger(formEl) {

                var submitButtonEl = formEl.querySelector('input[type=submit]');

                if (submitButtonEl) {
                    submitButtonEl.addEventListener('click', function() {
                        formEl.classList.add('_submit-attempted');
                    });
                }

            }

            addSubmitAttemptedTrigger(formEls[i]);

        }

    }

    addFormSubmitAttemptedTriggers();

});

Now forms that are attempted to be submitted will get a class _submit-attempted, so you can only give these fields a red box shadow:

input {
    box-shadow: none;
}

form._submit-attempted input {
    box-shadow: 0 0 5px #F00;
}
gitaarik
  • 42,736
  • 12
  • 98
  • 105
1

You can achieve this by chaining pseudo-classes:

<style>
  input:required:focus:invalid {
    ...
   }
</style>

that way the input field will just show invalid styles only when that input field required and focused.

Here is a helpful article: https://alistapart.com/article/forward-thinking-form-validation/

Another stack overflow thread on this: https://stackoverflow.com/a/7921385/11102617

dugong
  • 3,690
  • 4
  • 11
  • 27
1

I tried this in my website:

<style id="validation"></style>
<script>
function verify() {
    document.getElementById("validation").innerHTML = "input:invalid { border: 1px solid red!important;}input:valid { border: 1px solid green;}";
    }
</script>

Then add onclick="verify()" to your submit button, just like this:

<input type="submit" onclick="verify()">
1
form.invalid {
  *:invalid {
    box-shadow: 0px 0px 2px 1px red;
  }
}
let form = document.querySelectorAll('form')[0]
form.addEventListener('submit', function() {
  if (!form.checkValidity()) {
    form.classList.add('invalid')
  }
})

With the above styles and javascript the form controls will only display the :invalid styles after the submit event and the checkValidity check failing. An invalid check will add the invalid class to the form and activate the styles, otherwise the form will submit as usual.

akaspick
  • 1,541
  • 19
  • 17
  • If any input within the
    is invalid then the "submit" event handler will not be called when the form is submitted. For this approach to work you need to use ``
    ``.
    – RajV Aug 26 '22 at 10:38
0

for 'required' validation

way 1 - set 'required' attribute for each element on form submit

// submit button event
$('#form-submit-btn').click(function(event) {
    // set required attribute for each element
    $('#elm1, #elm2').attr('required','true');

    // since required attribute were not set (before this event), prevent form submission
    if(!$('#form')[0].checkValidity())
        return;

    // submit form if form is valid
    $('#form').submit();

});

way 2 - use 'data' attribute

<input type="text" data-required="1">

<script type="text/javascript">
// submit button event
$('#form-submit-btn').click(function(event) {
    // set required attribute based on data attribute
    $(':input[data-required]').attr('required','true');

    // since required attribute were not set (before this event), prevent form submission
    if(!$('#form')[0].checkValidity())
        return;

    // submit form if form is valid
    $('#form').submit();

});
</script>
abdulwadood
  • 596
  • 6
  • 14
0

the problem with waiting for an onSubmit or "submit" event listener is that the form itself is never technically being submitted when the data from the inputs is false. What I did (in react but same principle) is to set a click listener on the submit button and then set a class like "submitted".. then apply the classes accordingly

 .submitted input:invalid{
    outline: 2px solid red; /* oranges! yey */
}
Robert O'Toole
  • 197
  • 1
  • 11
0

Expanding on previous answers: Instead of adding a class to the form to indicate whether to show the :invalid styles, you can use a hidden input. The advantage is that when the form is cleared (either by a type=reset button or through javascript), the :invalid styles automatically disappear. You could add another event handler to remove a class on reset, of course; just my preference.

Note that to do it this way you have to use an invisible checkbox rather than a hidden element and specifically change the checked property. Changing an input's value or attribute through javascript changes the default value so far as .reset() is concerned. If you don't put a name on the dummy element, it won't be posted with the form, but you can still access it through javascript via id or class.

#attemptedSubmit[value=Yes] ~ :invalid, 
#attemptedSubmit[value=Yes] ~ * :invalid {
  border: 1px solid red;
}
<form>
   <input type="checkbox" style="display:none;" id="attemptedSubmit">
  ... rest of the form
  <input type="text" name="text" required>
  ... rest of the form
  <input type="submit" value="Submit" onclick="$('#attemptedSubmit').prop('checked', true)">
  <input type="reset" value="Clear">
</form>
Jay Irvine
  • 234
  • 4
  • 13
0

A way to do that with only CSS is to use the following properties, it allows to have styling for unfocused (input and input:placeholder), focused, valid, and error without having the error styling before any input from the user or if it's an empty input. It obviously requires a placeholder.

input:invalid:not(:placeholder-shown) {
    font-size: 1em;
    border-radius: 4px;
    color: var(--form-txt);
    background-color: var(--secondary);
    box-shadow: 0 0 10px var(--error);
}
sadeq shahmoradi
  • 1,395
  • 1
  • 6
  • 22
0

:user-invalid

Using :user-invalid (MDN), it is possible to get the desired effect. Unlike :invalid, it only activates after the user has interacted with the element or submitted the form.

Currently, :user-invalid is only supported in Firefox and Safari. It is not supported in Chrome and Edge.

#i1:invalid {
  outline: 3px red solid;
}

#i2:user-invalid {
  outline: 3px red solid;
}
<form>
  <div>
    <label>
      Required field (using <code>:invalid</code>):<br>
      <input required name="i1" id="i1">
    </label>
  </div>
  
  <br>
  
  <div>
    <label>
      Required field (using <code>:user-invalid</code>):<br>
      <input required name="i2" id="i2">
    </label>
  </div>
  
  <br>
  
  <button type="submit">Submit</button>
</form>
Flimm
  • 136,138
  • 45
  • 251
  • 267