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
indeterminateIDL 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:
-
indeterminateis 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>
You can only set it via JavaScript:
checkbox.indeterminate = true;
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
}
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 };
}
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?
- Yes
- No
- 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>
participation.addEventListener('change', () => {
answer.disabled = !participation.checked;
});
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.
Top comments (0)