Why does <legend> behave differently from <div>? - html

Consider this simple example, where we wish to layout a <legend> and its associated input content side-by-side, with no border, rather than using the default <fieldset> display. First, the markup that works as intended:
fieldset {
border: 0;
display: flex;
}
<form>
<fieldset>
<div>
<legend>Choose your favorite monster</legend>
</div>
<div>
<input type="radio" id="kraken" name="monster">
<label for="kraken">Kraken</label><br />
<input type="radio" id="sasquatch" name="monster">
<label for="sasquatch">Sasquatch</label><br />
</div>
</fieldset>
</form>
Now we think, "Isn't that <div> around the <legend> superfluous?" That is, can't we simply remove it, change the <legend> to display:block, and expect the same behavior?
It turns out we cannot:
fieldset {
border: 0;
display: flex;
}
legend {
display: block;
}
<form>
<fieldset>
<legend>Choose your favorite monster</legend>
<div>
<input type="radio" id="kraken" name="monster">
<label for="kraken">Kraken</label><br />
<input type="radio" id="sasquatch" name="monster">
<label for="sasquatch">Sasquatch</label><br />
</div>
</fieldset>
</form>
It now displays top-to-bottom, rather than side-by-side? But why? And is there a way to make it display side-by-side without the extra <div> around the <legend>?
EDIT:
Please note: I am looking for a solution that allows me to layout the two elements ("legend" and "input content") using flexbox. In particular, floating the <legend> is not a valid solution for my use case.

display: contents might be the best candidate here as it will remove the tag to keep only its content so no more issue with the behavior of <legend> tag. Then the flexbox algorithm will automatically make the text as an anonymous flex item so your legend is a flex item.
The element itself does not generate any boxes, but its children and pseudo-elements still generate boxes and text runs as normal. For the purposes of box generation and layout, the element must be treated as if it had been replaced in the element tree by its contents ref
fieldset {
border: 0;
display: flex;
}
legend {
display: contents;
}
<form>
<fieldset>
<legend>Choose your favorite monster</legend>
<div>
<input type="radio" id="kraken" name="monster">
<label for="kraken">Kraken</label><br />
<input type="radio" id="sasquatch" name="monster">
<label for="sasquatch">Sasquatch</label><br />
</div>
</fieldset>
</form>

Set the legend to float:left.
The rules for fieldset are somewhat "magic", but are described in the HTML5 rendering section.
If the [fieldset] element's box has a child box that matches the conditions in the list below, then the first such child box is the 'fieldset' element's rendered legend:
The child is a legend element.
The child's used value of 'float' is 'none'.
The child's used value of 'position' is not 'absolute' or 'fixed'.
position:absolute and position:fixed causes other issues. But float is perfect, because, since it's a flex item, it won't actually behave like a float, but as a regular flex item. I've added justify-content:space-around to demonstrate that that's actually happening.
fieldset {
border: 0;
display: flex;
justify-content:space-around;
}
legend {
display: block;
float:left;
}
<form>
<fieldset>
<legend>Choose your favorite monster</legend>
<div>
<input type="radio" id="kraken" name="monster">
<label for="kraken">Kraken</label><br />
<input type="radio" id="sasquatch" name="monster">
<label for="sasquatch">Sasquatch</label><br />
</div>
</fieldset>
</form>

Approach 1 (the floating <legend> hack)
As #Alohci discovered, when using <legend>, the float: left hack is (still) unavoidable.
See this answer by #BorisZbarsky from 2011, more than a decade ago and long before CSS Flexbox was properly established:
Legends are special. In particular, their default rendering can't be described in CSS, so browsers use non-CSS means of rendering them. What that means is that a statically positioned legend will be treated like a legend and be separate from the actual content of the fieldset.
Source: Why won't my <legend> element display inline?
Since a float: left declaration cannot be avoided, the most concise CSS I came up with is:
fieldset {
display: flex;
border: none;
}
legend {
float: left; /* Hack to prevent browsers applying special <legend> styling */
}
Working Example:
fieldset {
display: flex;
border: none;
}
legend {
float: left; /* Hack to prevent browsers applying special <legend> styling */
}
legend::after {
content: ':';
}
<form>
<fieldset>
<legend>Choose your favorite monster</legend>
<div>
<input type="radio" id="kraken" name="monster">
<label for="kraken">Kraken</label><br />
<input type="radio" id="sasquatch" name="monster">
<label for="sasquatch">Sasquatch</label><br />
</div>
</fieldset>
</form>
Further Reading:
This technical blogger ran up against the same issue. It seems like the unique positioning behaviour of <legend> has been an issue for a long time:
https://morgan.cugerone.com/blog/how-to-make-a-fieldset-legend-inline/
Approach 2 (the ARIA alternative)
The <legend> element has undisputed semantic value, but we can deploy:
aria-labelledby; or
aria-describedby
in another element (eg. <div>) to replicate the semantic value of <legend>.
If we swap out <legend> for <div id="my-legend"> and support with ARIA, we gain stylability without losing semantics.
Working Example:
fieldset {
display: flex;
border: none;
}
fieldset div:first-of-type::after {
content: ':';
}
<form>
<fieldset aria-describedby="my-legend">
<div id="my-legend">Choose your favorite monster</div>
<div>
<input type="radio" id="kraken" name="monster">
<label for="kraken">Kraken</label><br />
<input type="radio" id="sasquatch" name="monster">
<label for="sasquatch">Sasquatch</label><br />
</div>
</fieldset>
</form>

Related

Align right side of a group of labels

I'm making an HTML page with a list of fields for filling out billing info.
Since the labels are in-line, their width is determined by the text contained in them, thus making them different widths which makes the input boxes unaligned. This makes a very ugly, unorganized look.
<fieldset>
<legend>Billing Information</legend><br>
Card Number: <input type="text"><br><br>
Expiration Month: <input type="number"><br><br>
Expiration Year: <input type="number"><br><br>
Name on Card: <input type="text"><br><br>
Address: <input type="text"><br><br>
City: <input type="text"><br><br>
State: <input type="text"><br><br>
Country: <input type="text"><br><br>
ZIP Code: <input type="number"><br><br>
</fieldset><br>
What do I need to do to my HTML code to align the right side of the labels and the left side of the input boxes?
Use table structure for your lists of fields for proper alignment.
<fieldset>
<legend>Billing Information</legend>
<table>
<tr><td>Card Number:</td><td><input type="text"></td></tr>
<tr><td>Expiration Month:</td><td> <input type="number"></td></tr>
<tr><td>Expiration Year:</td><td> <input type="number"></td></tr>
<tr><td>Name on Card:</td><td> <input type="text"></td></tr>
<tr><td>Address:</td><td> <input type="text"></td></tr>
<tr><td>City:</td><td> <input type="text"></td></tr>
<tr><td>State:</td><td> <input type="text"></td></tr>
<tr><td>Country:</td><td> <input type="text"></td></tr>
<tr><td>ZIP Code:</td><td> <input type="number"></td></tr>
</table>
</fieldset>
You can use tables or use display: table-row & display: table-cell css to simulate table cell layouts as following:
fieldset {display: inline-block; padding: 10px 20px;} /* 'display: inline-block' is not necessary, just for appearance */
.row {display: table-row; }
.lbl {display: table-cell; text-align:right; padding: 8px 5px;}
<fieldset>
<legend>Billing Information</legend>
<div class="row"><span class="lbl">Card Number: </span><input type="text"></div>
<div class="row"><span class="lbl">Expiration Month: </span><input type="number"></div>
<div class="row"><span class="lbl">Expiration Year: </span><input type="number"></div>
<!-- ... /-->
</fieldset><br>
You will find it much easier if you put your elements into suitable containers and use CSS to do the hard work.
<style type="text/css">
label {
display: inline-block;
font-weight: bold;
width: 10em;
}
</style>
<fieldset>
<legend>Billing Information</legend><br>
<p><label for="card-number">Card Number:</label><input type="text" name="card-number" id="card-number"></p>
<p><label for="expiration-month">Expiration Month:</label><input type="text" name="expiration-month" id="expiration-month"></p>
<!-- etc -->
</fieldset>
Here the label does 3 jobs:
It creates a clickable text to activate your input box.
It makes the purpose of the text clear for screen readers and other software.
You can easily apply CSS to the element.
The label element can either be wrapped around the input element:
<label>Expiration Month: <input …></label>
or attached with the for attribute. The for attribute references an id, which is independent of the name attribute. As you see in the example, they are often set to the same value.
In the CSS, we set the width to something reasonable. However, the label needs to be displayed as an inline-block for the width to take effect.
Use flexbox to fix your problem.
Wrap the container fieldset using display: flex, Set the flex-direction as column. Wrap the children inside a div and set the flex-grow property for the span element as 1.
Using flexbox will take care of responsiveness.
Refer CSS Flexbox
fieldset {
display: flex;
flex-direction: column;
}
div {
display: flex;
margin: 10px;
}
span {
flex: 1;
}
<fieldset>
<legend>Billing Information</legend>
<div><span>Card Number: </span><input type="text"> </div>
<div><span>Expiration Month:</span> <input type="number"></div>
<div><span> Expiration Year:</span> <input type="number"></div>
<div><span>Name on Card:</span> <input type="text"> </div>
<div><span>Address:</span> <input type="text"> </div>
<div><span>City:</span> <input type="text"></div>
<div><span>State: </span><input type="text"></div>
<div><span>Country:</span> <input type="text"></div>
<div><span>ZIP Code:</span> <input type="number"></div>
</fieldset>

Radio Button Alignment Issues

How do I bring my radio buttons closer to the labels?
input[type="radio"]{
float: left;
width: auto;
margin-left: 3em;
}
<fieldset id="payment_method">
<legend>Payment Method</legend>
<input type="radio" name="payment_method"value="Bill Me">
<label for= "payment1"> Bill Me</label>
<input type="radio" name="payment_method"value="Bill Me">
<label for= "payment2">Credit Card</label>
</fieldset>
You do not need to float your inputs, you can just give the labels a negative margin instead like so:
label {
margin-left: -1px;
}
Just don't apply any rules to your radio input. As all form elements are non Block-level elements (excluding form itself) so you don't need to float them (in your case) and remove the extra margin. See the fiddle
input[type="radio"] {
/* No styling */
}
I like wrapping my input/label pairs in a <div> for easier styling. After that you could just remove the line break between the label and input tags:
<fieldset id="payment_method">
<legend>Payment Method</legend>
<div class="fieldgroup">
<input type="radio" name="payment_method"value="Bill Me"><label for= "payment1">Bill Me</label>
</div><!--/.fieldgroup-->
<div class="fieldgroup">
<input type="radio" name="payment_method"value="Bill Me"><label for= "payment2">Credit Card</label>
</div><!--/.fieldgroup-->
</fieldset>
It's not terribly pretty, but it is the most cross-browser compatible solution, since each browser treats inline-block spacing differently.
Or, if you want to keep your code tidy, you can use floats like you tried originally:
CodePen
input[type='radio'],
label {
float: left;
}
.fieldgroup:after {
content: "";
display: block;
clear: both;
}

How to correctly style a form?

I'm really not that good at CSS, and I want to know how to correctly style a form in a manner that it puts each single text input and label in a line. like this :
<label for="input1">...</label>
<input type="text" id="input1"/>
<label for="input2">...</label>
<input type="text" id="input2"/>
<label for="input3">...</label>
<input type="text" id="input3"/>
<label for="input3">...</label>
<input type="text" id="input3"/>
and it would be shown in the webpage like :
(label)(input)
(label)(input)
(label)(input)
(label)(input)
<label>foo</label>
<input type="text"/>
<label>foo</label>
<input type="text"/>​
<style>
input, label { float:left }
label { clear:left; }
</style>
​
http://jsfiddle.net/RpRS5/
I recommend this tutorial by A List Apart about Prettier Accessible Forms. You can also use a definition list with some custom styling, e.g.,
<dl><dt><label></label></dt>
<dd><input></dd></dl>
And something like:
dl dt {
float: left;
width: 8em;
}
Edit: to sum up the A List Apart article, they suggest you put form fields in an ordered list ol. Labels are displayed as inline-block so they appear horizontally next to their associated fields.
Put them in a list, or in a structure like a list (that is to say, wrap each "row" in a div).
Put your inputs inside the label element and then you can simply display: block them or float them, I prefer display but it would be easy enough to change.
<label>Hello <input type="radio" name="what" value="Hello" /></label>
http://jsfiddle.net/Bpxfp/
http://jsfiddle.net/ud7YE/1/
you can control the space between the label and input by varying the width of the wrapper. Just set the height of the label and the top margin of the input same in value but negative
I find enclosing label and input or select tags in a div or list. And the label and select tags should be of type inline-block
<div>
<label>Name: </label><input type="text" />
</div>
<div>
<label>Place: </label><input type="text" />
</div>
CSS:
label {
display: inline-block;
}
input {
display: inline-block;
padding: 2px;
}
div {
display: block;
margin: 2px 0;
}
This would work out well.

CSS format for checkboxes

I have a list of checkboxes, each one with a label:
<input type="checkbox" id="patient-birth_city" name="patient-birth_city" />
<label for="patient-birth_city">(_PATIENT_BIRTH_CITY_)</label>
<input type="checkbox" id="patient-birth_state" name="patient-birth_state" />
<label for="patient-birth_state">(_PATIENT_BIRTH_STATE_)</label>
<input type="checkbox" id="patient-birth_country" name="patient-birth_country" />
<label for="patient-birth_country">(_PATIENT_BIRTH_COUNTRY_)</label>
Without using any CSS they are showed in the same line (I suppose they have a default "inline" or "block-inline" display). The problem is I can't modify HTML structure and I need each pair checkbox-label appear in a new line. Like this. Is it possible using only CSS?
The good thing about label tags is you can wrap the input elements:
<label>
<input type="checkbox" id="birth_city" name="birth_city" />
City
</label>
<label>
<input type="checkbox" id="birth_state" name="birth_state" />
State
</label>
<label>
<input type="checkbox" id="birth_country" name="birth_country" />
Country
</label>
And if you add the following CSS:
label {
display: block;
}
It will display it how you want.
Demo here
As you CAN'T edit your HTML, this CSS would work:
input, label {
float: left;
}
input {
clear: both;
}
Demo here
Using float:left and clear:left you can do this with only css.
Demo: http://jsfiddle.net/VW529/2/
input {margin:3px;}
input, label {float:left;}
input {clear:left;}
The only problem is that the example does not show more information of parent elements, giving the container element overflow:hidden and/or clear:both might be needed to prevent floating elements next to the last label. (edited jsfiddle code with container div)

Style a <label> based on its <input>'s state

Is it possible, with only CSS, to style an HTML label dependent on its input's state?
In my case, I want to style an <input type="checkbox"> based on whether it's checked.
I tried putting the label inside the input, but Firefox and Chrome (at least) seems to parse them as siblings, even though they're clearly nested in the input source. And I don't know how to write a CSS rule that can indirect through a for= attribute.
Do I need to whip out the Javascript on this one?
They don't need to be nested, that's what the "for" attribute is for in the <label> element.
In modern browsers (those supporting CSS 2.1), you can use a sibling selector, such as
input + label {
/* rules */
}
You would have to have your markup in a strict sibling relationship, such as:
<input name="cb" id="cb" type="checkbox"><label for="cb">Checkbox</label>
Using the adjacent/sibling selector plus the attribute selector would make it work:
<form>
<style>
INPUT[checked=checked] + LABEL {
color: #f00;
}
</style>
<div>
<input type="checkbox" id="chk1" />
<label for="chk1">Label #1</label>
</div>
<div>
<input type="checkbox" id="chk2" checked="checked" />
<label for="chk2">Label #2</label>
</div>
</form>
To make this thing work you need to put the label after the input, this goes for text type inputs, so for checkbox you can skip this, unless you want the label before checkbox.
To keep the order for label being shown before the input you need to use Flexbox and reverse order of items, for example like this.
.form-group {
display: flex;
flex-direction: column-reverse;
}
The display: flex; with flex-direction: column-reverse; reorders the divs content.
Now all you need to do is use this to affect your label style.
input:checked + label {
color: #000;
}
And HTML for completeness.
<div class="form-group">
<input type="checkbox" name="rememberPwd" id="rememberPwd" class="form-input" required/>
<label for="rememberPwd">Remember?</label>
</div>