DEV Community

Cover image for Indeterminate Is Not a Value
bwi
bwi

Posted on

Indeterminate Is Not a Value

Indeterminate Is Not a Value

In Part 1, we established what a checkbox actually is: a binary control with presence-based semantics. We learned that "unchecked" doesn't mean "No" — it means "no participation."

Sometimes you need to represent more than two business states. Some UIs use a checkbox with a dash (—) or filled square for that.

This article explains what indeterminate means in HTML, what it does not mean, and the two distinct scenarios where it is commonly used.

Later, it also introduces a cleaner alternative when you truly need three business states: the participation pattern (checkbox + separate value control).


Part 1: Indeterminate Is Display-Only

What Indeterminate Looks Like

You've probably seen it: a checkbox that's neither checked nor unchecked, but shows a dash (—) or a filled square. This is the indeterminate state.

It looks like a third state. It feels like a third state.

It is not a third state.

What the Specification Actually Says

The HTML specification is explicit:

The indeterminate IDL attribute [...] gives the appearance of a third state.

Key word: appearance. It looks like a third state, but technically it isn't one.

And from MDN:

  • indeterminate is a JavaScript property only
  • There is no HTML attribute for it
  • It has no effect on form submission
  • It only changes how the checkbox looks

In other words:

Indeterminate is not a value. It's a visualization.

You Cannot Set It in HTML

This doesn't work:

<!-- This does nothing -->
<input type="checkbox" indeterminate>
Enter fullscreen mode Exit fullscreen mode

You can only set it via JavaScript:

checkbox.indeterminate = true;
Enter fullscreen mode Exit fullscreen mode

What Happens When the User Clicks?

Behavior on click: When the user clicks an indeterminate checkbox, the browser immediately clears the indeterminate state and toggles checked as normal.

The user cannot "select" indeterminate. They can only move away from it.


Part 2: Two Completely Different Reasons for Indeterminate

There are two completely different reasons to show an indeterminate checkbox — and they should not be treated as the same thing.

Reason A: Mixed (Aggregation)

Scenario: A parent checkbox with multiple child checkboxes.

  • All children checked → Parent shows checked
  • All children unchecked → Parent shows unchecked
  • Some children checked, some not → Parent shows indeterminate

What indeterminate means here:

"The children have different values."

Key characteristics:

  • The parent has no value of its own
  • The visual state is computed from children
  • Clicking the parent is a command (set all children to true/false)
  • Nothing should be stored for the parent

This is the correct use of indeterminate as defined by ARIA (the accessibility standard for web applications):

aria-checked="mixed" indicates a mixed mode value for a tri-state checkbox.

Reason B: Unset (Unknown/Never Answered)

Scenario: A checkbox that has never been interacted with.

  • User has never clicked it
  • No default value was set
  • The system doesn't know the answer

What indeterminate means here:

"No decision has been made yet."

Important distinction: From here on, we are no longer talking about the checkbox itself. The checkbox is still binary — it's still either ticked or not ticked. We are talking about the business meaning that the checkbox represents — what it means for your application or process.

Key characteristics:

  • There is no business value yet — not "yes", not "no", but "we don't know"
  • It's about user interaction history, not aggregation
  • First click should set a value (typically true)
  • The absence itself might be significant (e.g., "never answered" vs "answered no")

The Problem: Same Visual, Different Meaning

Both scenarios often use the same visual representation — the indeterminate dash.

But they mean completely different things:

Aspect Mixed (Aggregation) Unset (Unknown)
Source Computed from children Missing data
Has a domain value? No (derived) No (not yet decided)
Should be stored? Never Maybe (as "unknown")
User can select it? No No
What click does Command to children Sets initial value

When a system uses the same checkbox component for both without making the distinction explicit, bugs follow.


Part 3: The Value Question

A common question:

If a checkbox is indeterminate, what is its value?

Answer

A checkbox doesn't have an "indeterminate" value.

It has either:

  • true (checked)
  • false (unchecked)
  • no value (the concept is modeled elsewhere)

indeterminate is purely how it's displayed.

For Mixed (Parent Checkbox)

The parent checkbox has no value of its own.

// Parent state is DERIVED
function getParentState(children) {
    const checked = children.filter(c => c.checked);
    if (checked.length === 0) return { checked: false, indeterminate: false };
    if (checked.length === children.length) return { checked: true, indeterminate: false };
    return { checked: false, indeterminate: true }; // checked still exists but is visually suppressed when indeterminate is true
}
Enter fullscreen mode Exit fullscreen mode

Note: When indeterminate is true, the checked value is visually hidden but still exists. It just doesn't represent anything meaningful.

For Unset (Unknown)

The value is outside the checkbox:

// Domain model
type Answer = true | false | undefined;

// UI mapping
function toCheckboxState(answer: Answer) {
    if (answer === undefined) {
        return { checked: false, indeterminate: true };
    }
    return { checked: answer, indeterminate: false };
}
Enter fullscreen mode Exit fullscreen mode

The undefined lives in your model, not in the checkbox.

The checkbox never becomes tri-state. Only the meaning we project onto it does.


Part 4: The Participation Pattern

Remember from Part 1: "Unchecked means no participation."

What if you actually need three states?

  1. Yes
  2. No
  3. I don't want to answer / Not applicable

A single checkbox cannot do this. But two controls can:

The Pattern: Checkbox + Value Control

<label>
    <input type="checkbox" id="participation">
    I want to answer this question
</label>

<fieldset id="answer" disabled>
    <label><input type="radio" name="vote" value="yes"> Yes</label>
    <label><input type="radio" name="vote" value="no"> No</label>
</fieldset>
Enter fullscreen mode Exit fullscreen mode
participation.addEventListener('change', () => {
    answer.disabled = !participation.checked;
});
Enter fullscreen mode Exit fullscreen mode

Semantics:

  • Checkbox unchecked → "I don't want to participate" (no value sent)
  • Checkbox checked + Yes → "Yes"
  • Checkbox checked + No → "No"

This cleanly separates:

  • Participation (checkbox)
  • Value (radio/select)

Why This Is Better Than Tri-State

A tri-state checkbox tries to pack three meanings into one control:

  • Checked = Yes
  • Unchecked = No
  • Indeterminate = Unknown

But as we've seen:

  • Indeterminate has no value
  • The visual is ambiguous (mixed vs unknown)

The participation pattern is:

  • Explicit
  • Accessible
  • Unambiguous
  • Actually works with HTML semantics

Part 5: When to Use What

Use a Simple Checkbox When:

  • You need a binary opt-in (newsletter subscription)
  • "Not selected" means "does not apply" (consent checkboxes)
  • You're toggling a UI feature (show/hide advanced options)

Use Parent/Child Checkboxes When:

  • You have a hierarchical selection (folder contents)
  • "Select all" functionality
  • Parent state is always computed, never stored

Use the Participation Pattern When:

  • "No answer" is meaningfully different from "No"
  • You need to track whether something was explicitly answered
  • The question is optional but the answer must be definitive

Use Radio Buttons When:

  • You need explicit Yes/No
  • All options are mutually exclusive
  • "No selection" should not be possible

Use a Select/Dropdown When:

  • You have more than 2-3 options
  • You need a "Please select..." placeholder
  • Space is constrained

Part 6: Practical Rules

Based on everything we've discussed, here are practical rules for keeping checkbox semantics consistent:

1. Indeterminate Is Display-Only

Never try to store "indeterminate" as a value.

It's a visualization. Treat it that way.

2. Mixed Should Be Computed

Parent checkbox state = function of children. Always.

If you're storing a parent checkbox value alongside its children, you're creating a consistency problem.

3. Separate "Unknown" from "Mixed"

If both concepts exist in your system, they need different UI treatments.

Same visual + different meaning = bugs.

4. When In Doubt, Add a Control

If a checkbox feels overloaded, it probably is.

The participation pattern (checkbox + value control) is almost always clearer than a tri-state checkbox.

5. Make Assumptions Explicit

Document what "unchecked" means in your system.

Is it false? Is it null? Is it "not answered"? Is it "does not participate"?

If you can't answer this question clearly, neither can the next developer.


Summary

Concept What It Is What It's For
checked Boolean property The actual state
indeterminate Visual hint (JS only) Showing "mixed" or "unknown"
Mixed state Computed from children Parent/child hierarchies
Unknown/Unset Missing value in model Optional questions
Participation pattern Checkbox + separate control When you need 3+ states

The checkbox is still simple. It's binary, presence-based, and does exactly one thing.

The complexity comes from what we try to make it represent.


Further Reading

Top comments (0)