IttayD
IttayD

Reputation: 29123

Quad-state checkbox

I can have a tri-state checkbox with indeterminate, but I need to rotate 4 states. So how can I make something to look like a quad state checkbox? Is my only option to create a set of images to look like a checkbox with various icons or is there a more elegant way?

Upvotes: 0

Views: 2209

Answers (3)

myf
myf

Reputation: 11293

You can achieve similar effect (cycled state something) with radio buttons and a bit of CSS (CSS3), without JavaScript:

.cyclestate {
 display: inline-grid;
}
.cyclestate label {
 grid-area: 1 / 1;
 background-color: white; 
 color: black;
 z-index: 0;
 opacity: 0;
}
.cyclestate input {
 position: absolute;
 z-index: -1;
}
.cyclestate input:checked + label {
 opacity: 1;
}
.cyclestate input:first-of-type + label {
 z-index: 2;
}
.cyclestate input:checked + label + input + label {
 opacity: 0;
 z-index: 2;
}
/* -- Unrelated and accessibility -- */
.cyclestate { border: none; padding: 0; }
.cyclestate legend { position: absolute; }
.cyclestate label { padding: 1em; text-align: center; display: inline-block; cursor: pointer; user-select: none;
}
.cyclestate label::before { content: '← '; }
.cyclestate label::after { content: ' →'; }
.cyclestate label::before, label::after { color: transparent; }
.cyclestate input:focus-visible + label { outline: solid; }
.cyclestate input:focus-visible + label::before,
.cyclestate input:focus-visible + label::after { color: currentcolor; }
:root { background: dimgray; color: snow; }
:link { color: aqua; } :visited { color: lime; }
<fieldset class="cyclestate" id="x">
  <legend>Pick a number</legend>
  <input id="one" type="radio" name="r" checked>
  <label for="one">One</label>
  <input id="two" type="radio" name="r">
  <label for="two">Two</label>
  <input id="three" type="radio" name="r">
  <label for="three">Two Plus One</label>
  <input id="four" type="radio" name="r">
  <label for="four">Four</label>
</fieldset>

<p><button onclick="x.classList.toggle('cyclestate')">Toggle condenseness ↑</button>

Basically this approach overlays labels over each other (leveraging quite modern CSS techniques (grid)) and makes label of the NEXT unchecked input transparent and raised above all others. In effect what is visible is checked input's label, but click / tap events are captured by that transparent sibling overlay. This preserves semantics and accessibility.

(Caution: haven't thoroughly tested with screen readers nor older browsers.)


Original 2015 snippet with really dubious accessibility, use with caution (or better do not use at all):

.cyclestate input:not(b),
.cyclestate input:not(b) + label {
  display: none;
}
.cyclestate input:checked + label {
  display: inline-block;
  width: 4em;
  text-align: center;
  -webkit-user-select: none;
  -moz-user-select: none;
  -webkit-appearance: button;
  -moz-appearance: button;
}
<span class="cyclestate">
  <input type="radio" name="foo" value="one" id="one" checked>
  <label for="two">one</label>
  <input type="radio" name="foo" value="two" id="two">
  <label for="three">two</label>
  <input type="radio" name="foo" value="three" id="three">
  <label for="four">three</label>
  <input type="radio" name="foo" value="four" id="four">
  <label for="one">four</label>
</span>

With this you'll have space-conserving visual representation of multiple state at one place and readable value on the server side.

Be warned that such usage of label is a kinda exploit: label targets different input than it describes.

Upvotes: 8

Siguza
Siguza

Reputation: 23850

A checkbox can be made to look like it can have three states, but it will truly only ever have two.
Also, the "indeterminate" visual state is not reachable from the default user interface and has to be triggered programatically:

var box = document.getElementById('box'),
    checked = document.getElementById('checked'),
    indet = document.getElementById('indet');
var update = function()
{
    checked.textContent = box.checked;
    indet.textContent = box.indeterminate;
};
box.addEventListener('click', update);
document.getElementById('a').addEventListener('click', function()
{
    box.checked = true;
    update();
});
document.getElementById('b').addEventListener('click', function()
{
    box.checked = false;
    update();
});
document.getElementById('c').addEventListener('click', function()
{
    box.indeterminate = true;
    update();
});
document.getElementById('d').addEventListener('click', function()
{
    box.indeterminate = false;
    update();
});
<input type="checkbox" id="box"><br>
<button id="a">On</button> <button id="b">Off</button><br>
<button id="c">Indeterminate</button> <button id="d">Clear indeterminate</button>
<div>box.checked: <span id="checked"></span></div>
<div>box.indeterminate: <span id="indet"></span></div>

So to answer your question:

There is no native way to add a fourth visual (yet even actual) state to a checkbox, but this does not mean that you have to resort to images.
It's easy enough to style an element to look like a checkbox and use some JS to simulate a state rotation:

var state = 0;
var toggle = document.getElementById('toggle');
toggle.addEventListener('click', function()
{
    state = (state + 1) % 4;
    toggle.className = 'state' + state;
});
#toggle
{
    border: solid 1px #666;
    border-radius: 3px;
    width: 14px;
    height: 14px;
}
.state0
{
    background: linear-gradient(#DDD, #FFF);
}
.state1
{
    background: linear-gradient(#F00, #C00);
}
.state2
{
    background: linear-gradient(#FF0, #CC0);
}
.state3
{
    background: linear-gradient(#0F0, #0A0);
}
<div id="toggle" class="state0"></div>

But in the end you will have to use something other than actual checkboxes.

Apart from that, having a checkbox-like thing with more that two states is really counter-intuitive, and I kindly ask you to go with something that was made for multiple states, like radio buttons or a dropdown list.

Upvotes: 5

ThisClark
ThisClark

Reputation: 14823

It's not possible today. For others reading, a good reference about the using the three states including indeterminate: https://css-tricks.com/indeterminate-checkboxes/

You'll need to create a different solution using your own icons and in a sense create a custom form control. A possible design that fulfills your need to have multiple states:

Use a list of icons, like the star system in Gmail. When the star is clicked in Gmail it changes color and eventually after enough clicks (up to 10 I think) other icons like exclamation point and checkmark begin displaying.

Associate your different state requirements with the currently used icon (or index of the underlying icon list).

Upvotes: 0

Related Questions