name: handle-modus-checkbox-value-bug description: Apply the critical checkbox value inversion workaround when working with ModusCheckbox components
Handle Modus Checkbox Value Bug
Apply the critical value inversion workaround for ModusWcCheckbox components.
Critical Bug
The ModusWcCheckbox web component has a value inversion bug where the value property returns the opposite of the actual checked state:
- When checkbox is checked:
target.valuereturnsfalse - When checkbox is unchecked:
target.valuereturnstrue
This is the opposite of what developers expect.
When to Use
Use this skill when:
- Implementing checkbox functionality with ModusCheckbox
- Fixing checkbox-related bugs
- Handling checkbox value changes
- Creating forms with checkboxes
Reference Implementation
See src/components/ModusCheckbox.tsx:116-130 for the complete workaround implementation.
The Fix
Always invert the value when reading from checkbox events:
const handleValueChange = (event: Event) => {
const customEvent = event as CustomEvent<InputEvent>;
// 🚨 CRITICAL: Handle the value inversion bug
const rawValue = (customEvent.target as HTMLModusWcCheckboxElement).value;
const actualValue = !rawValue; // ✅ CORRECT: Invert the value
// Use actualValue for your logic
setChecked(actualValue);
};
Complete Pattern
import { useEffect, useRef } from "react";
import { ModusWcCheckbox } from "@trimble-oss/moduswebcomponents-react";
export default function ModusCheckbox({
value = false,
onValueChange,
}: {
value?: boolean;
onValueChange?: (event: CustomEvent<boolean>) => void;
}) {
const checkboxRef = useRef<HTMLModusWcCheckboxElement>(null);
useEffect(() => {
const checkbox = checkboxRef.current;
if (!checkbox) return;
const handleValueChange = (event: Event) => {
const customEvent = event as CustomEvent<InputEvent>;
// 🚨 CRITICAL BUG WORKAROUND: The ModusWcCheckbox component has a value
// inversion bug where the `value` property returns the opposite of the
// actual checked state. This function corrects this by inverting the
// raw value before passing it to the parent component.
const rawValue = (customEvent.target as HTMLModusWcCheckboxElement).value;
const actualValue = !rawValue; // ✅ CORRECT: Invert the value
// Create a new event with the corrected value
const correctedEvent = new CustomEvent("valueChange", {
detail: actualValue,
bubbles: true,
cancelable: true,
});
onValueChange?.(correctedEvent);
};
if (onValueChange) {
checkbox.addEventListener("inputChange", handleValueChange);
}
return () => {
if (onValueChange) {
checkbox.removeEventListener("inputChange", handleValueChange);
}
};
}, [onValueChange]);
return (
<ModusWcCheckbox
ref={checkboxRef}
value={value}
/>
);
}
Usage in Forms
When using checkboxes in forms:
function MyForm() {
const [agreeToTerms, setAgreeToTerms] = useState(false);
return (
<ModusCheckbox
value={agreeToTerms}
onValueChange={(event) => {
// event.detail is already corrected (inverted)
setAgreeToTerms(event.detail);
}}
label="I agree to the terms and conditions"
/>
);
}
Multiple Checkboxes
When handling multiple checkboxes:
function CheckboxGroup() {
const [checkboxes, setCheckboxes] = useState({
option1: false,
option2: true,
option3: false,
});
const handleCheckboxChange = (key: string, event: CustomEvent<boolean>) => {
// event.detail is already corrected
setCheckboxes(prev => ({
...prev,
[key]: event.detail,
}));
};
return (
<>
<ModusCheckbox
value={checkboxes.option1}
onValueChange={(e) => handleCheckboxChange('option1', e)}
label="Option 1"
/>
<ModusCheckbox
value={checkboxes.option2}
onValueChange={(e) => handleCheckboxChange('option2', e)}
label="Option 2"
/>
<ModusCheckbox
value={checkboxes.option3}
onValueChange={(e) => handleCheckboxChange('option3', e)}
label="Option 3"
/>
</>
);
}
Important Notes
- The bug is in
target.value: The inversion happens when readingevent.target.value, not inevent.detail - Always invert: Never trust the raw value - always invert it
- Wrapper handles it: The
ModusCheckboxwrapper component already handles this, so if you're using the wrapper, you don't need to invert again - Document the bug: Add comments explaining the workaround
Testing
To verify the fix works:
// Test that checked state matches expected value
<ModusCheckbox
value={isChecked}
onValueChange={(event) => {
console.log('Raw value (inverted):', event.detail);
console.log('Expected checked:', isChecked);
// These should match after inversion
}}
/>
Common Mistakes
- Forgetting to invert: Using raw value directly
- Double inversion: Inverting when using the wrapper component (it already handles it)
- Wrong event: Using
inputChangeinstead of readingtarget.value
Related Files
src/components/ModusCheckbox.tsx- Complete wrapper implementation.cursor/rules/modus-checkbox-value-inversion-react.mdc- Detailed documentation