Forms
Contents:
Missing.css aims to style HTML nicely without authors needing to concern themselves over anything other than using HTML tags with correct meanings, but this is not always feasible. Forms are a particularly complex part of HTML, with multiple ways to mark up the same semantics. For instance, you can label an element in multiple ways:
<form>
<label>Name <input></label>
<!-- or... -->
<label for=adr>Address</label> <input id=adr>
</form>
Because of this, it's not really possible to write a stylesheet that will work with any HTML form.
Missing.css will work best on forms that follow these markup conventions:
§ Inputs and labels
Inputs inside labels will be display:inline.
Inputs outside labels will be display:block.
Input placeholders are styled with text-align:end to better distinguish them from actual input.
<form class="flex-switch">
<div>
<label for=category>Category:</label>
<select id=category placeholder="Select a category...">
<option>Blues
<!-- ... -->
</select>
</div>
<div>
<label for=search>Search:</label>
<input id=search type=search placeholder="Search...">
</div>
<div>
<label for=text>Text:</label>
<input id=text type=text placeholder="Type...">
</div>
</div>
§ Buttons
Wrap a <button> in a <strong> tag to get a primary button.
Buttons support the aria-pressed and aria-expanded attributes.
Buttons, .<button> masquerades, and <input type=file> all support colorways as well.
<script>
const toggle = el => (el.ariaPressed = (el.ariaPressed !== 'true'))
</script>
<section tabindex=0 class="overflow:auto">
<table id=button-table class="table">
<caption>Button demonstration</caption>
<thead>
<tr><th><th><th><code>.info</code><th><code>.ok</code><th><code>.warn</code><th><code>.bad</code>
<tbody>
<tr><th scope=row><code><button></code>
<td><button>Plain</button>
<td><button class="ok">Open</button>
<!-- ... -->
<tr><th scope=row class="padding-inline-start"><code>:disabled</code>
<td><button disabled>Plain</button>
<td><button disabled class="ok">Open</button>
<!-- ... -->
<tr><th scope=row class="padding-inline-start"><code>[aria-pressed=true]</code>
<td><button aria-pressed=true onclick="toggle(this)">Plain</button>
<td><button aria-pressed=true class="ok" onclick="toggle(this)">Open</button>
<!-- ... -->
<tr><th scope=row><code><strong><button></code>
<td><strong><button>Plain</button></strong>
<td><strong><button class="ok">Open</button></strong>
<!-- ... -->
<tr><th scope=row class="padding-inline-start"><code>:disabled</code>
<td><strong><button disabled>Plain</button></strong>
<td><strong><button disabled class="ok">Open</button></strong>
<!-- ... -->
<tr><th scope=row class="padding-inline-start"><code>[aria-pressed=true]</code>
<td><strong><button aria-pressed=true onclick="toggle(this)">Plain</button></strong>
<td><strong><button aria-pressed=true class="ok" onclick="toggle(this)">Open</button></strong>
<!-- ... -->
<tr><th scope=row><code><a class="<button>"></code>
<td><a href=#button-table class="<button>">Plain</button>
<td><a href=#button-table class="ok <button>">Open</button>
<!-- ... -->
</table>
</section>
.info | .ok | .warn | .bad
| ||
|---|---|---|---|---|---|
<button>
| |||||
:disabled
| |||||
[aria-pressed=true]
| |||||
<strong><button>
| |||||
:disabled
| |||||
[aria-pressed=true]
| |||||
<a class="<button>">
| Plain | Info | Open | Reset | Close |
§ Tabular forms
You can use the .table and .rows classes to create a form with inputs lined up like cells of a table.
Tabular forms default to wide inputs, but can be overridden with the .narrow-inputs utility class.
<form class="table rows">
<p><label for=inp>Label</label> <input id=inp></p>
...
§ Labeling radio buttons
The accepted way to label a group of radio buttons is to use <fieldset> and <legend>:
<fieldset>
<legend>Color</legend>
<ul>
<li><label><input type=radio name=color value="ff0000">Red</label>
<li><label><input type=radio name=color value="00ff00">Green</label>
<li><label><input type=radio name=color value="0000ff">Blue</label>
</ul>
</fieldset>
This works in missing.css, but these two elements are [notorious] for being hard to style.
You can use the following pattern instead, which will work with tabular forms.
Notice that the wrapper has role=radiogroup and its aria-labelledby value refers to the id of the label for the whole radiogroup.
<form class="table rows narrow-inputs">
<div>
<label for=item>Item</label>
<select id=item>
<option>Wallet
...
</select>
</div>
<div role=radiogroup aria-labelledby=color-lbl>
<span id=color-lbl>Color</span>
<div>
<div><label><input type=radio name=color value="ff0000"> Red</label></div>
<div><label><input type=radio name=color value="00ff00"> Green</label></div>
<div><label><input type=radio name=color value="0000ff"> Blue</label></div>
</div>
</div>
</form>
§ Selects
Missing.css will attempt to style <select> elements consistently across browsers.
Support for the new customizable select API is implemented, as are colorways.
Warning: While browser support is improving, please be aware that many browsers still force their native dropdown select picker styles.
In the spirit of progressive enhancement, missing.css implements a few hacks to add partial colorway support to unsupported browsers.
Once a browser supports appearance: base-select, appearance will be standardized.
Please be sure to review your implementation across multiple browsers.
Tip: Closing tags for <optgroup> and <option> can typically be omitted.
You can also use <optgroup disabled> to disable an entire group of <options>.
Depending on the attributes specified, <select> elements can be divided into the following categories:
- single-select dropdowns,
- single-select listboxes,
- multi-select listboxes, and
- multi-select dropdowns.
Checkmarks can be enabled by using the .checks or .checkboxes variant classes on the <select> (provided the viewer's browser supports them).
Checkmarks will be placed on the inline-start side unless the .flip utility class is also added to the <select>.
By default, <options> inside of a single-select will be rendered without their ::checkmark pseudo-element and multi-selects will be equivalent to <select multiple class="checks">.
Each of the examples below highlights a different combination of these classes and attributes.
§ Single-select dropdowns
Select elements without the multiple or size attributes will render as single-select dropdowns.
<form class="flex-row flex-wrap:wrap">
<!-- ... -->
<div>
<label for=single:dropdown:warn:bg>Warn:</label>
<select id=single:dropdown:warn:bg class="warn bg flip checkboxes">
<optgroup label="Enabled">
<option>One
<option>Three
<!-- ... -->
<optgroup label="Disabled" disabled>
<option>Two
<option>Four
<!-- ... -->
</select>
</div>
<!-- ... -->
</form>
§ Select listboxes
Selects with either size or multiple attributes will be styled as listboxes.
In the following subsection, we discuss the special case of <select multiple size=1>.
Info: Keep in mind that <option selected> specifies which option(s) should be selected by default.
When updating selection with JavaScript, use the el.checked property to ensure the :checked pseudo-class triggers correctly.
Avoid toggling the selected or aria-selected=true HTML attributes in the DOM for dynamic updates.
Warning: Browsers that support appearance: base-select utilize a different set of keyboard controls.
The updated controls are designed to be more accessible and result in a single tab stop that moves focus into the <select> element.
This allows keyboard users to navigate between the options with arrow keys and activate them using Space or Enter.
The older style of navigation relied on Ctrl + Arrow to navigate between <options> while the <select> maintains focus.
<div class="flex-switch wide-inputs">
<!-- ... -->
<div>
<label for=multi:listbox:bad>Mathematicians:</label>
<select id=multi:listbox:bad multiple size=8 class="bad flip checkboxes"
<optgroup label="Analysts">
<option>Stefan Banach
<option selected>Augustin Cauchy
<!-- ... -->
</select>
<p class="<small> crowded">Choose multiple</p>
</div>
<!-- ... -->
</div>
Choose one
Choose one
Choose one
Choose multiple
Choose multiple
Choose multiple
§ Multi-select dropdowns
Some browsers render <select multiple size=1> as a multi-select dropdown.
Support for this implementation will improve as customizable select continues to roll out.
Until then, consider erring on the side of caution since browsers could render an otherwise unusable widget.
<div class="flex-switch wide-inputs">
<!-- ... -->
<div>
<label for=multi:dropdown:ok>Categorized:</label>
<select id=multi:dropdown:ok size=1 multiple class="ok">
<optgroup label="Analysts">
<option>Stefan Banach
<option selected>Augustin Cauchy
<!-- ... -->
</select>
<p class="<small> crowded">Choose multiple</p>
</div>
<!-- ... -->
</div>
Choose multiple
Choose multiple
Choose multiple
§ New parsing rules
Finally, missing.css will not interfere with the new <selectedcontent> element, if authors wish to use it.
The customizable select API also allows the <legend> element to be used to provide a label inside of an <optgroup> instead of the label attribute.
We suggest retaining the label attribute for backwards compatibility with older parsers.
<select>
<button>
Selected: <selectedcontent></selectedcontent>
</button>
<optgroup label="Saxophonists">
<legend>Saxophonists</legend>
<option>Coleman Hawkins
<option>Charlie Parker
<option>John Coltrane
<option>Sonny Rollins
<option>Hank Mobley
<optgroup label="Trumpeters">
<legend>Trumpeters</legend>
<option>Buddy Bolden
<option>Louis Armstrong
<option>Dizzy Gillespie
<option>Miles Davis
</select>
§ Progress bars
Create a progress bar using the <progress> element.
This element should be used to represent how much of a specific, ongoing process has been completed.
Be sure to add a <label> for accessibility (in conjunction with .vh or <v-h> if you like).
The element can be put in an indeterminate state by not including the value attribute.
Indeterminate <progress> elements will show a pending animation if the user does not have @prefers-reduced-motion set.
When utilizing a vertical writing-mode, the indeterminate animation can be fixed by adding the .vertical class.
The element can be styled by setting --border-width, --border-style, and --border-radius variables directly on the <progress> element.
When not explicitly set, the element inherits from --interactive-border-width, --interactive-border-style, and --tab-border-radius.
For full-width progress bars, use the .inline-size:100% utility class.
Colorways are supported.
<div class="flex-column">
<label for=p1 class="vh">Upload progress...</label>
<progress id=p1 value=0.5 class="inline-size:100%"></progress>
<label for=p2>LCARS Scan...</label>
<progress id=p2 class="ok inline-size:100%" value=0.25 style="block-size: 1.5em; --border-width: 6px; --border-style: double; --border-radius: 0 .5em"></progress>
<label for=p3>Virus progress...</label>
<progress id=p3 class="warn inline-size:100%" value=0.75 style="--border-width: 3px; --border-style: inset; --border-radius: 0 .5rem .5rem .5rem"></progress>
<label for=p4>Indeterminate Cylon</label>
<progress id=p4 class="bad" style="inline-size: 3em;">Indeterminate Cylon</progress>
</div>
§ Meters
Use the <meter> element to create a meter gauge.
This element is used to indicate a measurement within a known range and is semantically different from the <progress> element.
Similar to the <progress> element, you can style a <meter> by setting --border-width, --border-style, and --border-radius directly on the element.
The <meter> element derives its colors from the .ok, .warn, and .bad colorways.
Warning: Due to cross-browser implementation differences, colorways are only fully supported in browsers that pass the @supports (selector(:-moz-meter-optimum)) check.
The color of the meter bar is correctly set in all browsers according to the values of --ok-fg, --warn-fg, and --bad-fg.
However, only browsers passing the @supports rule will also have the appropriate colorway background and border colors.
A suitable fallback choice has been made for these colors (--plain-bg for the background and --interactive-bg for the border) until browser support improves.
<strong>Disk usage (optimum is a medium amount of usage)</strong>
<label for=disk1>60GB Used:</label>
<meter id=disk1 min=0 max=100 value=60 low=30 high=70 optimum=50>60GB of 100GB</meter>
<strong>Battery level (optimum is full)</strong>
<label for=battery1>85% Charged:</label>
<meter id=battery1 min=0 max=100 value=85 low=20 high=70 optimum=100>85% charged</meter>
<strong>Temperature (optimum is low)</strong>
<label for=temp1>15°C:</label>
<meter id=temp1 min=0 max=50 value=15 low=20 high=30 optimum=0>15°C</meter>
Disk usage (optimum is a medium amount of usage)
Battery level (optimum is full)
Temperature (optimum is low)
<strong>Disk usage (getting full)</strong>
<label for=disk2>80GB Used:</label>
<meter id=disk2 min=0 max=100 value=80 low=30 high=70 optimum=50>80GB of 100GB</meter>
<strong>Battery level (getting low)</strong>
<label for=battery2>25% Charged:</label>
<meter id=battery2 min=0 max=100 value=25 low=20 high=70 optimum=100>15% charged</meter>
<strong>Temperature (overheating)</strong>
<label for=temp2>40°C:</label>
<meter id=temp2 min=0 max=50 value=40 low=10 high=30 optimum=0>40°C</meter>
<strong>Exam score (failing)</strong>
<label for=exam1>20% Score:</label>
<meter id=exam1 min=0 max=100 value=20 low=40 high=80 optimum=100>20% score</meter>
Disk usage (getting full)
Battery level (getting low)
Temperature (overheating)
Exam score (failing)