195

There is no way to have a tri-state check button (yes, no, null) in HTML, right?

Are there any simple tricks or work-arounds without having to render the whole thing by oneself?

Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • Functionally, what's the difference between 'no' and 'null' in your usage? – David Thomas Nov 12 '09 at 23:27
  • 5
    Look at the answers below (except for mine). "Null" means something like "No answer"... – Franz Nov 12 '09 at 23:32
  • 2
    Exactly. It's an item with ancestors and "null" means "use parent value". – Pekka Nov 12 '09 at 23:54
  • 9
    An alternative usage for a tri-state checkbox is when searching/filtering. For example, let's assume I've got an interface to search a list of used cars. One of the search options may be whether or not the car has a sunroof. The three states could be used as follows: Only show cars WITH a sunroof Only show cars WIHTOUT a sunroof Show both Now, certainly, this can be handled a number of ways... we could us a dropdown, or we could use a set of three radio buttons... but a tri-state checkbox (that is either empty, has a green check, or has a red x in it) is also a nice solution. – Mir Dec 15 '11 at 23:37
  • 4
    It would be useful for a list of checkboxes, with one checkbox at the head of the list to either check or uncheck all the remaining boxes. If all are checked, then the header checkbox can reflect that. If all are unchecked then the header checkbox can reflect that too. If the list contains a combination of checked and unchecked boxes, then showing the header checkbox in an intermediate state would provide visual feedback. That header checkbox would never carry a value - it is just a visual cue and UI aid. Being able to switch the head checkbox between all three states also would be handy. – Jason Aug 07 '17 at 16:02
  • It's been ages (12 years since asked) but it might be a good choice to accept pau.moreno's answer as it answers the question perfectly. – Kerwin Sneijders May 13 '21 at 23:11
  • Another site that rips off from stackoverflow: https://www.thetopsites.net/article/58913608.shtml contsins a lot of this thread. – Patanjali Nov 05 '21 at 09:56

18 Answers18

142

Edit — Thanks to Janus Troelsen's comment, I found a better solution:

HTML5 defines a property for checkboxes called indeterminate

See w3c reference guide. To make checkbox appear visually indeterminate set it to true:

element.indeterminate = true;

Here is Janus Troelsen's fiddle. Note, however, that:

  • The indeterminate state cannot be set in the HTML markup, it can only be done via Javascript (see this JSfiddle test and this detailed article in CSS tricks)

  • This state doesn't change the value of the checkbox, it is only a visual cue that masks the input's real state.

  • Browser test: Worked for me in Chrome 22, Firefox 15, Opera 12 and back to IE7. Regarding mobile browsers, Android 2.0 browser and Safari mobile on iOS 3.1 don't have support for it.

Previous answer

Another alternative would be to play with the checkbox transparency for the "some selected" state (as Gmail does used to do in previous versions). It will require some javascript and a CSS class. Here I put a particular example that handles a list with checkable items and a checkbox that allows to select all/none of them. This checkbox shows a "some selected" state when some of the list items are selected.

Given a checkbox with an ID #select_all and several checkboxes with a class .select_one,

The CSS class that fades the "select all" checkbox would be the following:

.some_selected {
    opacity: 0.5;
    filter: alpha(opacity=50);
}

And the JS code that handles the tri-state of the select all checkbox is the following:

$('#select_all').change (function ()
{
    //Check/uncheck all the list's checkboxes
    $('.select_one').attr('checked', $(this).is(':checked'));
    //Remove the faded state
    $(this).removeClass('some_selected');
});

$('.select_one').change (function ()
{
  if ($('.select_one:checked').length == 0)
      $('#select_all').removeClass('some_selected').attr('checked', false);
  else if ($('.select_one:not(:checked)').length == 0)
      $('#select_all').removeClass('some_selected').attr('checked', true);
  else
      $('#select_all').addClass('some_selected').attr('checked', true);
});

You can try it here: http://jsfiddle.net/98BMK/

starball
  • 20,030
  • 7
  • 43
  • 238
pau.moreno
  • 4,407
  • 3
  • 35
  • 37
  • 13
    I [modified it to use HTML5's indeterminate attribute](http://jsfiddle.net/ysangkok/UhQc8/). – Janus Troelsen Sep 29 '12 at 00:00
  • 1
    Tested in chrome v35 and the "all" check box only checks the other boxes on the first time clicked. After this it only works to un-check the other check boxes. Fixed in this version: http://jsfiddle.net/CHRSb/1/ – rdans Jun 18 '14 at 13:06
  • 3
    `.prop('checked', ...)` should be used instead of `.attr('checked', ...)`. – Luna Jul 15 '15 at 13:45
  • I fixed the bugs Ross pointed out; [new fiddle here](http://jsfiddle.net/b81nsu7p/). – Mike DeSimone Dec 01 '15 at 18:40
  • Fortunately the CSS Tricks article (https://css-tricks.com/indeterminate-checkboxes/) has a plain js version that doesn't require a 92k!!! library for such an almost trivial task. – Patanjali Nov 05 '21 at 10:14
  • I implore people providing answers to use the minimal number of dependencies possible. Don't assume people are using large libraries. In general, libraries require ongoing updating and substantially increase intrusion risk vectors. – Patanjali Nov 05 '21 at 10:20
49

You could use HTML's indeterminate IDL attribute on input elements.

Ms2ger
  • 15,596
  • 6
  • 36
  • 35
  • 3
    According to Bert Bos in an email from 2002 (), IE/Win and IE/Mac have supported it since version 4. Firefox appears to have implemented it in version 3.6. It also appears to have been implemented in Safari in 2008. I think that makes not a lot of end users. – Ms2ger Mar 04 '13 at 10:16
  • use this url (http://help.dottoro.com/ljuocesd.php) instead. It shows who supports it (everyone bar opera), since when, and visually displays what it is. People are scared of the spec pages. +1 for this not-a-plugin solution – Hashbrown Oct 04 '13 at 06:07
18

My proposal would be using

  • three appropriate unicode characters for the three states e.g. ❓,✅,❌
  • a plain text input field (size=1)
  • no border
  • read only
  • display no cursor
  • onclick handler to toggle thru the three states

See examples at:

/**
 *  loops thru the given 3 values for the given control
 */
function tristate(control, value1, value2, value3) {
  switch (control.value.charAt(0)) {
    case value1:
      control.value = value2;
    break;
    case value2:
      control.value = value3;
    break;
    case value3:
      control.value = value1;
    break;
    default:
      // display the current value if it's unexpected
      alert(control.value);
  }
}
function tristate_Marks(control) {
  tristate(control,'\u2753', '\u2705', '\u274C');
}
function tristate_Circles(control) {
  tristate(control,'\u25EF', '\u25CE', '\u25C9');
}
function tristate_Ballot(control) {
  tristate(control,'\u2610', '\u2611', '\u2612');
}
function tristate_Check(control) {
  tristate(control,'\u25A1', '\u2754', '\u2714');
}
<input type='text' 
       style='border: none;' 
       onfocus='this.blur()' 
       readonly='true' 
       size='1' 
       value='&#x2753;' onclick='tristate_Marks(this)' />

<input style="border: none;"
       id="tristate" 
       type="text"  
       readonly="true" 
       size="1" 
       value="&#x2753;"  
       onclick="switch(this.form.tristate.value.charAt(0)) { 
     case '&#x2753': this.form.tristate.value='&#x2705;'; break;  
     case '&#x2705': this.form.tristate.value='&#x274C;'; break; 
     case '&#x274C': this.form.tristate.value='&#x2753;'; break; 
       };" />  
Ravi Makwana
  • 2,782
  • 1
  • 29
  • 41
Wolfgang Fahl
  • 15,016
  • 11
  • 93
  • 186
6

You can use radio groups to achieve that functionality:

<input type="radio" name="choice" value="yes" />Yes
<input type="radio" name="choice" value="No" />No
<input type="radio" name="choice" value="null" />null
Franz
  • 11,353
  • 8
  • 48
  • 70
  • 1
    I know that, thanks. :) I am currently using selects, but the form is getting a little cluttered. – Pekka Nov 12 '09 at 23:23
  • 1
    Well, then what exactly would the third state look like anyways? Just like when the checkbox is disabled? How would you want to trigger that? – Franz Nov 12 '09 at 23:24
5

Here is a runnable example using the mentioned indeterminate attribute:

const indeterminates = document.getElementsByClassName('indeterminate');
indeterminates['0'].indeterminate  = true;
<form>
  <div>
    <input type="checkbox" checked="checked" />True
  </div>
  <div>
    <input type="checkbox" />False
  </div>
  <div>
    <input type="checkbox" class="indeterminate" />Indeterminate
  </div>
</form>

Just run the code snippet to see how it looks like.

gil.fernandes
  • 12,978
  • 5
  • 63
  • 76
4

You can use an indeterminate state: http://css-tricks.com/indeterminate-checkboxes/. It's supported by the browsers out of the box and don't require any external js libraries.

altso
  • 2,311
  • 4
  • 26
  • 40
3

I think that the most semantic way is using readonly attribute that checkbox inputs can have. No css, no images, etc; a built-in HTML property!

See Fiddle:
http://jsfiddle.net/chriscoyier/mGg85/2/

As described here in last trick: http://css-tricks.com/indeterminate-checkboxes/

Positivity
  • 5,406
  • 6
  • 41
  • 61
2

Like @Franz answer you can also do it with a select. For example:

<select>
  <option></option>
  <option value="Yes">Yes</option>
  <option value="No">No</option>
</select>

With this you can also give a concrete value that will be send with the form, I think that with javascript indeterminate version of checkbox, it will send the underline value of the checkbox.

At least, you can use it as a callback when javascript is disabled. For example, give it an id and in the load event change it to the javascript version of the checkbox with indeterminate status.

PhoneixS
  • 10,574
  • 6
  • 57
  • 73
2

Besides all cited above, there are jQuery plugins that may help too:

for individual checkboxes:

for tree-like behavior checkboxes:

EDIT Both libraries uses the 'indeterminate' checkbox attribute, since this attribute in Html5 is just for styling (https://www.w3.org/TR/2011/WD-html5-20110113/number-state.html#checkbox-state), the null value is never sent to the server (checkboxes can only have two values).

To be able to submit this value to the server, I've create hidden counterpart fields which are populated on form submission using some javascript. On the server side, you'd need to check those counterpart fields instead of original checkboxes, of course.

I've used the first library (standalone checkboxes) where it's important to:

  • Initialize the checked, unchecked, indeterminate values
  • use .val() function to get the actual value
  • Cannot make work .state (probably my mistake)

Hope that helps.

Frank Sebastià
  • 107
  • 2
  • 9
1

Refering to @BoltClock answer, here is my solution for a more complex recursive method:

http://jsfiddle.net/gx7so2tq/2/

It might not be the most pretty solution but it works fine for me and is quite flexible.

I use two data objects defining the container:

data-select-all="chapter1"

and the elements itself:

data-select-some="chapter1"

Both having the same value. The combination of both data-objects within one checkbox allows sublevels, which are scanned recursively. Therefore two "helper" functions are needed to prevent the change-trigger.

Community
  • 1
  • 1
MFH
  • 33
  • 7
1

Here other Example with simple jQuery and property data-checked:

 $("#checkbox")
  .click(function(e) {
    var el = $(this);

    switch (el.data('checked')) {

      // unchecked, going indeterminate
      case 0:
        el.data('checked', 1);
        el.prop('indeterminate', true);
        break;

        // indeterminate, going checked
      case 1:
        el.data('checked', 2);
        el.prop('indeterminate', false);
        el.prop('checked', true);
        break;

        // checked, going unchecked
      default:
        el.data('checked', 0);
        el.prop('indeterminate', false);
        el.prop('checked', false);

    }
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label><input type="checkbox" name="checkbox" value="" checked id="checkbox"> Tri-State Checkbox </label>
Ravi Makwana
  • 2,782
  • 1
  • 29
  • 41
1

As I needed something like this -without any plug-in- for script-generated checkboxes in a table... I ended up with this solution:

<!DOCTYPE html>
<html>
<body>

Toto <input type="checkbox" id="myCheck1" onclick="updateChkBx(this)" /><br />
Tutu <input type="checkbox" id="myCheck2" onclick="updateChkBx(this)" /><br />
Tata <input type="checkbox" id="myCheck3" onclick="updateChkBx(this)" /><br />
Tete <input type="checkbox" id="myCheck4" onclick="updateChkBx(this)" /><br />

<script>
var chkBoxState = [];

function updateChkBx(src) {
  var idx = Number(src.id.substring(7)); // 7 to bypass the "myCheck" part in each checkbox id
  if(typeof chkBoxState[idx] == "undefined") chkBoxState[idx] = false; // make sure we can use stored state at first call
  
  // the problem comes from a click on a checkbox both toggles checked attribute and turns inderminate attribute to false
  if(chkBoxState[idx]) {
    src.indeterminate = false;
    src.checked = false;
    chkBoxState[idx] = false;
  }
  else if (!src.checked) { // passing from checked to unchecked
    src.indeterminate = true;
    src.checked = true; // force considering we are in a checked state
    chkBoxState[idx] = true;
  }
}
// to know box state, just test indeterminate, and if not indeterminate, test checked
</script>

</body>
</html>
Faeldihn
  • 11
  • 1
1

A short snippet using an auxiliary variable and indeterminate:

cb1.state = 1

function toggle_tristate(cb) {
    cb.state = ++cb.state % 3   // cycle through 0,1,2
    if (cb.state == 0) {
        cb.indeterminate = true;
        cb.checked = true;   // after 'indeterminate' the state 'false' follows 
    }
}
<input id="cb1" type="checkbox" onclick="toggle_tristate(this)">

Only state==0 is captured. The rest is handle automatically.

http://jsfiddle.net/6vyek2c5

0

You'll need to use javascript/css to fake it.

Try here for an example: http://www.dynamicdrive.com/forums/archive/index.php/t-26322.html

Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
0

It's possible to have HTML form elements disabled -- wouldn't that do? Your users would see it in one of three states, i.e. checked, unchecked, and disabled, which would be greyed out and not clickable. To me, that seems similar to "null" or "not applicable" or whatever you're looking for in that third state.

AmbroseChapel
  • 11,957
  • 7
  • 46
  • 68
  • Very good idea, but I need the user to be able to click it again. I fear that on a disabled checkbox, I will have trouble firing events and so on. – Pekka Nov 13 '09 at 10:35
  • you can still see the value of a disabled checkbox, which is confusing to the user if the value is not important. – Janus Troelsen Sep 28 '12 at 22:38
  • I think 'null' is mean to to represent the user not supplying an answer to the question. It doesn't necessarily mean that the question is not applicable. – bdsl Mar 24 '16 at 11:04
0

Building on the answers above using the indeterminate state, I've come up with a little bit that handles individual checkboxes and makes them tri-state.

MVC razor uses 2 inputs per checkbox anyway (the checkbox and a hidden with the same name to always force a value in the submit). MVC uses things like "true" as the checkbox value and "false" as the hidden of the same name; makes it amenable to boolean use in API calls. This snippet uses a third hidden state to persist the last request values across submits.

Checkboxes initialized with the below will start indeterminate. Checking once turns on the checkbox. Checking twice turns off the checkbox (returning the hidden value of the same name). Checking a third time returns it to indeterminate (and clears out the hidden so a submit will produce a blank).

The page also populates another hidden (e.g., triBox2Orig) with whatever value was on the query string to start, so the 3 states can be initialized and persisted between submits.

$(document).ready(function () {
    var initCheckbox = function (chkBox)
    {
        var hidden = $('[name="' + $(chkBox).prop("name") + '"][type="hidden"]');
        var hiddenOrig = $('[name="' + $(chkBox).prop("name") + 'Orig"][type="hidden"]').prop("value");
        hidden.prop("origValue", hidden.prop("value"));
        if (!chkBox.prop("checked") && !hiddenOrig) chkBox.prop("indeterminate", true);
        if (chkBox.prop("indeterminate")) hidden.prop("value", null);
        chkBox.change(checkBoxToggleFun);
    }

    var checkBoxToggleFun = function ()
    {
        var isChecked = $(this).prop('checked');
        var hidden = $('[name="' + $(this).prop("name") + '"][type="hidden"]');
        var thirdState = isChecked && hidden.prop("value") === hidden.prop("origValue");
        if (thirdState) {   // on 3rd click of a checkbox, set it back to indeterminate
            $(this).prop("indeterminate", true);
            $(this).prop('checked', false);
        }
        hidden.prop("value", thirdState ? null : hidden.prop("origValue"));
    };

    var chkBox = $('#triBox1');
    initCheckbox(chkBox);

    chkBox = $('#triBox2');
    initCheckbox(chkBox);
});
Timbergus
  • 3,167
  • 2
  • 36
  • 35
user1664043
  • 695
  • 5
  • 14
0

There's a simple JavaScript tri-state input field implementation at https://github.com/supernifty/tristate-checkbox

Peter
  • 3,305
  • 2
  • 15
  • 5
0

The jQuery plugin "jstree" with the checkbox plugin can do this.

http://www.jstree.com/documentation/checkbox

-Matt

Matt Frear
  • 52,283
  • 12
  • 78
  • 86