Two small form demos without a lick of JavaScript...

I’ve always tried to push the limits of what I can do with a purely CSS solution before jumping to a JavaScript implementation. Not because I think that all problems should be solved by CSS alone, I’ve actually run into many examples where a CSS only solution was actually detrimental to accessibility. (I’ll have a much bigger article on that someday soon…)

With that said, I wanted to share two small CSS only form demos that don’t require any JavaScript to function that will hopefully inspire you to see how far you can push the limits of a no JS solution.

Demo 1:
Simple URL Validation Tooltip

As the title implies, this demo simply displays a tooltip explaining that a URL must start with either http:// or https://.

Granted, this is kind of a silly thing to do, as you don’t really need to have those when entering a URL into a browser these days. But this is a demo of a concept after all.

Here is the mark-up:

<form class="form">
  <div class="container">
    <label for="url">Enter Your Website:</label>
    <input type="url" id="url" pattern="https?://.+" placeholder="" aria-describedby="message" />

    <div class="invalid-message" role="tooltip" id="message">
        Be sure to enter http:// or https://

To make sure that the user types in the URL correctly, a pattern attribute was added to the input, asking for pattern="https?://.+". When the user focuses in the input, and starts typing, the tool tip will appear to let them know to meet the pattern’s requirement, and then disappear once the requirement has been met.

To add some extra accessibility to the tooltip, an attribute of aria-describedby="message" was added to the input, mapping to the div with an ID of #message and an ARIA attribute role="tooltip".

Now for the CSS:

Note: I’ve only included the CSS necessary to make the form function properly. The rest is all specifically for the demo’s styling.

The first rule sets up the styling for the tooltip, but also hides it by default.

To get a fade-in effect for the tool tip, I’m using a CSS transition on the opacity.

Pointer Events are turned off because even though the div is invisible, it still exists as an element in the DOM, and could potentially make content underneath it unclickable. By setting pointer-events: none; we remove this potential UX issue.

.invalid-message {
  background: rgba(255,0,0,.2);
  left: 0;
  opacity: 0;
  padding: 8px;
  pointer-events: none;
  position: fixed;
  top: 0;
  transition: opacity .2s;
  width: 100%;

Next, in the instance of an invalid entry, we use the general sibling selector to find the .invalid-message and set it’s opacity to 1, and pointer-events back to auto, to make it visible again.

:invalid ~ .invalid-message {
  opacity: 1;
  pointer-events: auto;

Demo 2:
Name & Email Form

Here we have a simple form asking for a Name and Email address with a submit button that won’t be clickable until the form is filled out properly.

Here is the mark-up:

<form class="form-container" aria-describedby="messages">

    <label class="input-label" for="user_name">Name:</label>
    <input type="text" id="user_name" placeholder="John Smith" required />

    <label class="input-label" for="user_email">Email:</label>
    <input type="email" id="user_email" placeholder="" required />

    <button type="submit" class="btn btn-submit">submit</button>

    <p class="messages" role="tooltip" id="messages">
      <span class="msg-required">
        You still need to enter the following:<br />
        <b class="msg-invalid-name">Full Name,</b>
        <b class="msg-invalid-email">email address</b>

      <b class="msg-valid">
        You can now submit the form!


Both of the inputs are set to required, so a valid user input for both of them must be entered before the submit button can be clicked. The name field has no restrictions on it, but the email input has it’s own default validation parameters to it that must be met before the input will be considered valid.

The paragraph with the class messages contains the text that informs the user what they still need to do to allow the form to be submitted.

This form also has some ARIA hooks applied to it, specifically role="tooltip" on the messages paragraph and an aria-describedby="message" attribute on the form, linking it to the #messages paragraph.

As for the CSS:

There’s actually very little CSS to get this to work, but let’s go over it bit by bit.

Note: Again, I’ve only included the CSS necessary to make the form function properly. The rest is all specifically for the demo’s styling.

First, we set all messages to be hidden from both the browsers and screen readers.

input[id="user_email"]:valid ~ .messages .msg-invalid-email,
input[id="user_name"]:valid ~ .messages .msg-invalid-name {
  display: none;
  height: 0;
  visibility: hidden;

We also don’t want the form to be submitted unless the inputs are filled out properly. So as long as one or both of those inputs are invalid, the following CSS will make the submit button ghosted and unclickable.

input:invalid ~ .btn-submit {
  opacity: .4;
  pointer-events: none;

Next, depending on the state of form, we need to hide or show the appropriate information in the #messages paragraph. Using the general sibling selector, we can make the invalid messages appear, ie. .msg-required as long as an input is still invalid.

However, if all the in inputs become valid, the invalid message will then go away, and the .msg-valid then can appear.

input:invalid ~ .messages .msg-required,
input[id="user_name"]:valid ~ input[id="user_email"]:valid ~ .messages .msg-valid {
  display: block;
  height: auto;
  visibility: visible;

And just to really make sure people notice the change, visually, we’ll set the valid message to the color green.

.msg-valid {
  color: green;

In Closing...

While these two examples focus on CSS only methods to display messages when filling out forms, I hope it inspires you to play around with other ways to create CSS only alternatives for what you may otherwise default to JavaScript (or jQuery) to create.

I’d also like to make note that while I made these demos before seeing @heydonworks Practical ARIA Examples, I was inspired by his article to update my demos’ ARIA support. So thank you @heydonworks.