Page 1 of 1

Explore: CSS Styling and Forms Rate Topic: -----

#1 andrewsw  Icon User is offline

  • It's just been revoked!
  • member icon

Reputation: 3806
  • View blog
  • Posts: 13,500
  • Joined: 12-December 12

Posted 18 November 2014 - 10:24 AM

This is a follow-up to my recent blog entry that provides an HTML form to demonstrate all of the HTML, and HTML5, form-elements and attributes, without any styling. This tutorial is an exploration and discussion of some of the issues that you will encounter when attempting to style a form with CSS. In fact, a lot of the discussion applies to CSS styling in general, not just to forms.

The completed form is not beautiful, and not perfect. It is (in my opinion) rather neatly aligned and consistently laid out, which were my main interests while building it. The purpose of this tutorial is to discuss and explore issues that arise when styling forms.



Styling a form is a large subject and you will find many tutorials and articles on it. There are online form builders, Javascript frameworks, and even companies that will design and create a form for you (at a cost).

What makes styling a form a challenge is, in two words, the browsers. Each browser renders each form-element slightly differently, using different CSS properties and different defaults.

Hint: Do not attempt to achieve pixel perfection with your form design. It is a fruitless task. Test your form frequently in different browsers (and different browser versions). You are looking for the form to be functional, attractive, and easy to navigate. In summary: You want your user(s) to be willing and able to complete your form. It is not necessary - and not possible - that the form should look identical in every browser.



Attached Image

I am an HTML and CSS minimalist. I am loathe to add unnecessary (non-semantic) HTML elements simply to solve a layout issue (although I will do this on occasion if it provides the simplest solution, as you will discover). I want the smallest CSS file possible: no unnecessary rules. (I want the browser to do most of the work!)

When attempting to solve an issue with CSS I take the following approach:

  • Make sure my HTML and CSS are correct and valid.
  • Use my browser's developer tools to explore, and experiment with, CSS properties. Hopefully this will help me to discover where the main issue lies, and what properties I should concentrate on first to lead to a solution.
  • Change or add some properties and test the results.
  • I try to add only one or two properties at a time.

There is no point in adding 10 properties in one go. If it doesn't work then I'll just have to remove them. But.. a couple of these properties may have provided (or pointed to) the solution, but I won't know this, and I won't know which ones. (Besides, adding one or two properties at a time gives you what I shall call a visual progress map. You can see where you are heading, and might pick up some clues to the solution.)

(cont.)
  • If the properties haven't helped I remove them.

Do not just keep adding rules, hoping a solution will suddenly materialize. You might get lucky but you've probably added a lot of unnecessary rules, and your next issue will be harder to solve.

Don't just add more class-names to the problem, and adding more detailed selectors, or !important (which is a sin!), should not be your goto approach. These are just disguising the real problem, and, again, making your next issue harder to solve.



When I/you have solved the problem the temptation is to mentally tick it off and.. carry on. Don't do this. Review the changes you have made. Try removing one of the properties you have added to see whether it was part of the solution. If it wasn't, then remove it. Keep doing this, and exploring your changes. This process will 1) increase your understanding of CSS 2) help you to understand what the real issue was, and 3) keep your CSS lean and mean.

Here is the full code:
<!DOCTYPE html>
<html>
<head>
    <title>HTML5 Form</title>
<style>
body {
    font-family: Verdana, Arial, Helvetica, sans-serif;
}
form {
    width: 400px;
    /*margin: 10px auto; to centre */
    padding: 10px 20px;
    border-radius: 15px;
    background-color: lightblue;
    font-size: 12px;
}
fieldset {
    border: 1px solid black;
    margin-top: 5px;
    padding: 4px 8px;
}
fieldset.open {
    border: none;
}
fieldset.centre {
    text-align: center;
}
fieldset.centre legend {
    text-align: left;
}
legend {
    font-weight: bold;
}
label {
    display: inline-block;
    margin-top: 3px;
    margin-bottom: 3px;
}
label span {
    display: inline-block;
    width: 100px;
    height: 16px;
    text-align: right;
    margin-right: 15px;
}
label span.inline {
    width: auto;
    margin-right: 0;
}
input, select {
    font-size: 12px;
    padding: 2px;
}
input:required {
    border: 2px inset red;
}
input[type='date'] {
    height: 16px;
}
input[type='range'] {
    position: relative;
    bottom: -5px;
}
/*input[type='radio'], input[type='checkbox'] {
    vertical-align: -2px;
}*/
#btnSubmit {
    float: right;
    padding: 4px 8px;
}
</style>
</head>
<body>
<form id="frmPersonal" action="processpersonal.php" method="POST">
<fieldset>
    <legend>Name:</legend>
    <label><span>First Name:</span>
    <input type="text" name="firstname" size="20" maxlength="30" required></label>
    <label><span>Last Name:</span>
    <input type="text" name="lastname" size="20" maxlength="30" required></label>
    <label><span>Title:</span>
        <select name="selTitle">
            <option value="NA" selected>(select)</option>
            <option value="Mr">Mr</option> 
            <option value="Mrs">Mrs</option>
            <option value="Miss">Miss</option>
            <option value="Ms">Ms</option>
            <option value="Dr">Dr</option>
        </select>
    </label>
</fieldset>
<fieldset class="open">
<!-- Using a fieldset with no border is the easiest way 
to keep this single input inline with the others. -->
<label><span>Date of Birth:</span>
<input type="date" name="dob"></label>
</fieldset>
<fieldset class="centre">
    <legend>Method of Payment:</legend>
    <label><span class="inline">Credit Card:</span>
    <input type="radio" name="payment" value="ccard" checked></label>
    <label><span class="inline">Debit Card:</span>
    <input type="radio" name="payment" value="dcard"></label>
    <label><span class="inline">PayPal:</span>
    <input type="radio" name="payment" value="ppal"></label>
</fieldset>
<fieldset class="centre">
    <legend>Language(s):</legend>
    <label><span class="inline">C/C++</span>
    <input type="checkbox" name="languages[]" value="ccpp"></label>
    <label><span class="inline">Java</span>
    <input type="checkbox" name="languages[]" value="java"></label>
    <label><span class="inline">C#</span>
    <input type="checkbox" name="languages[]" value="csharp"></label>
    <label><span class="inline">VB.NET</span>
    <input type="checkbox" name="languages[]" value="vbnet"></label>
    <label><span class="inline">Javascript</span>
    <input type="checkbox" name="languages[]" value="js"></label>
</fieldset>
<fieldset class="centre">
    <legend>Years Programming:</legend>
    <label><span class="inline">Years:</span>
    <input type="number" name="years" min="0" max="20" step="1" value="0"></label>
    <label><span class="inline">Or Use Range: 0</span>
    <input type="range" name="yearsR" min="0" max="20" step="5" value="0">20</label>
</fieldset>
<fieldset>
    <legend>Contact:</legend>
    <label><span>Telephone:</span><input type="tel" name="telephone"></label>
    <label><span>Email:</span><input type="email" name="email" size="30" required></label>
    <label><span>Web Site:</span><input type="url" name="website"size="30"></label>
</fieldset>
<fieldset class="centre">
    <legend>Comments:</legend>
    <textarea name="comments" rows="4" cols="40" placeholder="Your comments.."></textarea>
</fieldset>
<fieldset class="open">
    <input type="submit" id="btnSubmit" name="btnSubmit" value="Submit">
</fieldset>
</form>
</body>
</html>


Here is a typical piece of the HTML:
<fieldset>
    <legend>Name:</legend>
    <label><span>First Name:</span>
    <input type="text" name="firstname" size="20" maxlength="30" required></label>


You will often see code using a DIV to contain a label and its related form-element, with these two elements adjacent, rather than nested as I have done. This is a design decision and the approach depends on the size, type and style of your form. Using a table to contain, and help align, form elements is also very common.

My form is essentially a two-column design (considering the labels and their adjacent inputs). If you have a four-column design, with two sets of labels and inputs sitting next to each other, then using a table could be considered. For a simple form (which most are) a table is unnecessary.

Let's break down the CSS and discuss why it was added.
form {
    width: 400px;
    /*margin: 10px auto; to centre */
    padding: 10px 20px;
    border-radius: 15px;
    background-color: lightblue;
    font-size: 12px;
}


I am using a fixed, and reasonable, width for the form. Building a responsive form (responsive web design) is not part of this discussion.

I am using padding within the form to create an empty area around (outside of) all the form elements. Much simpler than mucking around with margins for the controls themselves.

You can change the background colour!

I am establishing a default font-size within the form, hoping that it will cascade to all the controls. It won't. This demonstrates one of the main issues with form-styling. CSS doesn't cascade properly, and different control-types will ignore this setting, and others such as the font(s) (and many others). Nevertheless, this will affect the labels and other elements such as SPANs. I will tweak other form-elements as necessary to achieve consistency.

(The default font-size for the page should be set on the body.)



A different approach that many favour is to use a css reset. What this achieves is to remove, or specifically set to zero, none, or some reasonable (initial) value, a large number of CSS properties. That is, to level the playing field across the different browsers. This doesn't solve every form-element styling issue but, if it appeals to you, make sure you apply it before any other CSS rules, and use it throughout your site.



fieldset {
    border: 1px solid black;
    margin-top: 5px;
    padding: 4px 8px;
}


fieldsets have a default border, I'm replacing this with a specific one. I've given the form a round border, so perhaps you will want the fieldsets to have round borders as well? It is worth noting that when round borders first became available people went overboard with them, and everything had to have a round border!

Try removing the margin-top, the fieldsets will be too close to each other. Again, different browsers may use different default margins for fieldsets, possibly 0. I'm taking charge of this setting. In contrast, the padding is a decrease from the defaults.

I decided to use fieldsets throughout. This is probably a little overwhelming for such a short form, but this is partly because I deliberately kept the form short so that I could provide a single screenshot (without zooming). They could be styled to make the form appear less cluttered.

fieldset.open {
    border: none;
}
fieldset.centre {
    text-align: center;
}
fieldset.centre legend {
    text-align: left;
}


Initially I didn't place the date-input (date of birth) in a fieldset and, of course, it did not align with the other inputs. I wanted all the text-inputs (and date, email, etc., inputs) to align vertically to their left edge. I think this is an important visual effect even though, if you were to ask the user, they probably wouldn't be aware (consciously) that this alignment is present.

[The telephone, etc., inputs aren't exactly aligned with the other inputs, but you wouldn't know this without using a ruler.]

Placing the date-input in a fieldset, but without a legend and removing the border, is the simplest way to keep the inputs aligned. I suppose it would be possible to keep them aligned without a fieldset but I didn't fancy the task. I'm using a class-name so that I now have a template for other inputs that I may want to appear on a line by themselves.

My approach may be frowned upon. A fieldset without a legend is a non-semantic element, added purely for layout. I might try and argue that the main purpose of a fieldset is (or was) purely visual anyway, despite the presence of a legend. I am not too concerned: it is a nice, simple, solution.

The centre class is used with the radio buttons and checkboxes. text-align: center is the simplest solution, rather than, for example, putting them in a DIV, giving this DIV a fixed-width and using margin: 0 auto. I have no reason (currently) to introduce this extra DIV. I am labouring this point because people sometimes overlook this simple use of text-align.

(There is a bit more CSS work required with the radio button and checkboxes, discussed shortly.)

fieldset.centre legend Centring the text also causes the legend to be centred. Note that I have avoided creating an (unnecessary) class-name for these legends, just to bring them back to the left. A legend will only be centred when it appears within a fieldset of class-name centre.
legend {
    font-weight: bold;
}


Is this necessary, or effective? I think it looks okay.
label {
    display: inline-block;
    margin-top: 3px;
    margin-bottom: 3px;
}
label span {
    display: inline-block;
    width: 100px;
    height: 16px;
    text-align: right;
    margin-right: 15px;
}


People take, and recommend, a number of approaches to aligning text next to inputs. Specifically, keeping them vertically centred: sharing the same mid-line. Using display: table-cell so that vertical-align can be used, using tables, sometimes making use of line-height. Using display: inline-block tends to be a good solution (or the first step towards a solution) to many alignment issues. To complete the solution my approach is to ensure that the elements (the spans and inputs) occupy the same vertical space. The spans are given a height of 16px and their font-size is 12px, inherited from the form's rule. The inputs will (shortly) be given a font-size of 12px (because this isn't inherited from the form) and padding of 2px. 12+2+2 = 16.

Floating?
Spoiler

width: 100px; This value can be increased a little if necessary.
label span.inline {
    width: auto;
    margin-right: 0;
}


This is for the radio buttons and checkboxes, to remove the previous settings. I could have used fieldset.centre span rather than creating a new class (inline) but I wanted to retain the option to choose between the two styles for the spans (inside of labels).

You might notice that the radio buttons and checkboxes are not aligned vertically with their labels (spans). This doesn't concern me. Pursue it if it interests you. However, here is a quick and easy solution that works:
input[type='radio'], input[type='checkbox'] {
    vertical-align: -2px;
}


Attached Image
input, select {
    font-size: 12px;
    padding: 2px;
}


These elements don't inherit the font-size from the form. As mentioned, I want, and need, these elements to occupy the same vertical space as their adjacent spans, to keep them all aligned neatly. Note that a select does not respect a height setting.
input:required {
    border: 2px inset red;
}


:required is a pseudo-class. Check the support for this in different browsers as a lot of people still prefer to add the class-name 'required' to these elements and target this class-name in the CSS instead. I've used inset as otherwise the elements aren't the same width as inputs without the required attribute.
input[type='date'] {
    height: 16px;
}


I want the date-input to be the same height as the other inputs, but I also need this to vertically align it with its span.
input[type='range'] {
    position: relative;
    bottom: -5px;
}


Without this the range-input sits too high on the line. I could have taken an approach similar to that for the other inputs to keep it vertically-inline, but it is a fairly detailed object and I doubted that I could achieve a consistent look cross-browser. I considered that this input is always going to be higher on the line than it should be, so decided on a simplistic approach just to pull it down a little.

Besides which, I don't think I'll be using this element. If I want a slider I'll favour a Javascript or jQuery version.
#btnSubmit {
    float: right;
    padding: 4px 8px;
}


This is the only element that is targeted with an id. There will normally only be one submit button for a form, and it will require individual styling. Even if there is more than one form on a page, each submit button is likely to be styled differently. An alternative is to target the submit button within the form, #frmPersonal input[type='submit'].

Some people make efforts to align the submit button to the right edge of other controls. That's fine, but bear in mind that this submit is visually different from other controls, and so it may not look like it is aligned. I just float it to the right, and might use margins to bring it in a little bit.

Returning to the HTML:
    <label><span>Telephone:</span><input type="tel" name="telephone"></label>
    <label><span>Email:</span><input type="email" name="email" size="30" required></label>
    <label><span>Web Site:</span><input type="url" name="website"size="30"></label>


There is no reason to expect all text-inputs to have the same width. Some forms do attempt this, but you would have to accept that many inputs will be much wider than they need to be.
<fieldset class="centre">
    <legend>Comments:</legend>
    <textarea name="comments" rows="4" cols="40" placeholder="Your comments.."></textarea>
</fieldset>


I first inserted a label (a span) and the textarea into a fieldset, following the previous pattern. It was, of course, significantly different in appearance, and alignment, than the other elements. Rather than spend ages formatting the textarea and its label (and trying to position the label) I realised that the textarea is a significant enough feature to be contained in a fieldset of its own. Centring it, and foregoing a label in favour of the fieldset's legend, is a nice, straight-forward, approach.

I haven't applied any styling to the textarea. I would probably use #frmPersonal textarea to do this because, if there were more than one textarea, they are very likely to be formatted the same.

Final Notes

I add to the CSS in more or less the order that I add and format elements on the page. However, when I encounter an element that has already appeared in the CSS I will back-up to place it with the same, or similar, elements. For example, when I added a class to a fieldset I scrolled up to where the earlier fieldset rules appeared in the CSS. Doing this helps to avoid creating repeated rules and selectors, and keeps things organised.

I repeat my advice not to seek pixel-perfection with your forms. If you are not yet convinced then you might read this article:

The Problem Of CSS Form Elements :Smashing Magazine

Otherwise, you might consider something like formalize.me to help conquer your forms.

This post has been edited by andrewsw: 21 November 2014 - 03:37 PM


Is This A Good Question/Topic? 2
  • +

Replies To: Explore: CSS Styling and Forms

#2 andrewsw  Icon User is offline

  • It's just been revoked!
  • member icon

Reputation: 3806
  • View blog
  • Posts: 13,500
  • Joined: 12-December 12

Posted 21 November 2014 - 01:53 PM

The form also looks reasonable at 300px width rather than 400px, requiring only a few minor changes.

Attached Image

Reducing the size and cols of the email and url inputs and the textarea:

Spoiler

Reducing the width and right-margin of the span (inside each label):

Spoiler

Reducing the padding of the fieldsets and/or of the form could also have been considered.

This was part of my plan. That is, not to create a fully responsive form (it is still of a fixed width) but, by using a minimal amount of CSS, to make changing the design only require a small number of changes.

This post has been edited by andrewsw: 21 November 2014 - 01:58 PM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1