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.

Example: Placeholder markup
<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.

Example: Button markup
<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>&lt;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>&lt;strong>&lt;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>&lt;a class="&lt;button>"></code>
        <td><a href=#button-table class="<button>">Plain</button>
        <td><a href=#button-table class="ok <button>">Open</button>
        <!-- ... -->
  </table>
</section>

Button demonstration
.info.ok.warn.bad
<button>
:disabled
[aria-pressed=true]
<strong><button>
:disabled
[aria-pressed=true]
<a class="<button>"> Plain Info Open Reset Close
Example: File input button markup

§ 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.

Example: Tabular form markup
<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.

Example: Radio group markup for tabular forms
<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>

Color

§ 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:

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.

Example: Single-select dropdown markup
<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.

Example: Single- and multi-select listbox markup
<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.

Example: Multi-select dropdown markup
<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.

Example: Expanded select markup
<select>
  <button>
  Selected:&nbsp;<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.

Example: Progress bar markup
<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>

Indeterminate Cylon

§ 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.

Example: Value in optimum range
<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) 60GB of 100GB

Battery level (optimum is full) 85% charged

Temperature (optimum is low) 15°C

Example: Value in sub-optimum range
<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) 80GB of 100GB

Battery level (getting low) 15% charged

Temperature (overheating) 40°C

Exam score (failing) 20% score