Fix iCheck Checkboxes Not Working on Tablets
Overview​
All iCheck-styled checkboxes and radio buttons in Ultimate POS stop responding to touch/tap on modern tablets (iPadOS 13+, some Android tablets). Tapping a checkbox does nothing — no visual change, no state change.
This affects every page that uses iCheck: POS settings, product forms, role permissions, manufacturing module, and more.
Problem Explanation​
Ultimate POS uses the iCheck library to style checkboxes and radio buttons. iCheck wraps each native <input> in a styled <div> and places an invisible <ins class="iCheck-helper"> overlay on top to capture mouse events.
iCheck detects mobile devices using this user agent check:
_mobile = /ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent)
The problem: Starting with iPadOS 13 (2019), Apple changed the iPad's user agent to match desktop Safari — the string "iPad" is no longer present. This means _mobile = false on modern iPads.
When _mobile is false, iCheck's internal touchend handler executes return false, which calls both preventDefault() and stopPropagation(). This kills the synthetic click event that the browser would normally generate after a touch. Since iCheck only toggles checkboxes on click events (not on touchend), the checkbox never toggles.
In short:
- User taps checkbox on tablet
touchendfires on the iCheck helper element- iCheck's handler runs
return false(because it thinks it's a desktop browser) preventDefault()prevents the browser from generating a syntheticclick- No
click= no toggle = checkbox appears broken
Solution​
Add a capturing-phase touchend listener that fires before iCheck's own handler, toggles the checkbox, and blocks iCheck from interfering. A second capturing-phase click listener blocks the synthetic click (on devices where it still fires) to prevent double-toggling.
Step 1: Locate the iCheck Initialization​
Open public/js/app.js and find the iCheck initialization block:
//initialize iCheck
$('input[type="checkbox"].input-icheck, input[type="radio"].input-icheck').iCheck({
checkboxClass: 'icheckbox_square-blue',
radioClass: 'iradio_square-blue',
});
Step 2: Add the Touch Fix​
Paste the following code immediately after the iCheck initialization block:
// Fix iCheck not responding to touch/tap on tablets (iPadOS 13+, etc).
// Root cause: iCheck detects mobile via UA string, but modern iPads report
// as desktop Safari. When _mobile=false, iCheck does "return false" on
// touchend which kills the synthetic click — so the checkbox never toggles.
// Fix: capturing-phase touchend toggles the checkbox, then a capturing-phase
// click handler blocks the synthetic click to prevent double-toggle.
(function() {
var _iCheckTouched = false;
function findICheckWrapper(target) {
var $target = $(target);
var $wrapper = $target.closest('.icheckbox_square-blue, .iradio_square-blue');
if (!$wrapper.length) {
var $label = $target.closest('label');
if ($label.length) {
$wrapper = $label.find('.icheckbox_square-blue, .iradio_square-blue');
}
}
return $wrapper;
}
document.addEventListener('touchend', function(e) {
var $wrapper = findICheckWrapper(e.target);
if ($wrapper.length) {
var $input = $wrapper.find('input');
if ($input.length && !$input.prop('disabled')) {
e.stopPropagation();
_iCheckTouched = true;
setTimeout(function() { _iCheckTouched = false; }, 500);
if ($wrapper.hasClass('checked')) {
$input.iCheck('uncheck');
} else {
$input.iCheck('check');
}
}
}
}, true);
// Block synthetic click that fires ~300ms after touchend to prevent
// iCheck's click handler from double-toggling the checkbox back.
document.addEventListener('click', function(e) {
if (_iCheckTouched && findICheckWrapper(e.target).length) {
e.stopPropagation();
e.preventDefault();
}
}, true);
})();
That's it. Only one file needs to change (public/js/app.js). The fix is global — it applies to all iCheck checkboxes and radio buttons across the entire application.
How It Works​
The fix uses two capturing-phase event listeners registered on document. Capturing-phase listeners fire before any target or bubbling-phase handlers (including iCheck's directly-bound handlers).
touchend listener (capturing phase)​
- User taps a checkbox on a tablet
- Our capturing listener fires before iCheck's handler
- Finds the iCheck wrapper (
.icheckbox_square-blueor.iradio_square-blue) - Toggles the checkbox using iCheck's API (
$input.iCheck('check')/$input.iCheck('uncheck')) - Calls
e.stopPropagation()to prevent iCheck's owntouchendhandler from running - Sets a
_iCheckTouchedflag for 500ms
click listener (capturing phase)​
- On some devices/browsers, a synthetic
clickstill fires ~300ms after the touch - If
_iCheckTouchedistrue, the click is blocked withstopPropagation()+preventDefault() - This prevents iCheck's
clickhandler from toggling the checkbox a second time (double-toggle)
Why this doesn't affect desktop​
Desktop browsers don't fire touchend events (only mouse events), so the touchend listener never triggers. Desktop behavior is completely unchanged.
Why this works for dynamically added checkboxes​
The listeners are on document (not on individual elements), and use $(e.target).closest() to find the wrapper. This works for elements added to the DOM at any time — no re-initialization needed.
How to Test​
- Open your application in Chrome
- Press
F12to open DevTools - Click the device toggle toolbar button (or press
Ctrl+Shift+M) - Select a tablet device (e.g. iPad Air)
- Hard-refresh the page with
Ctrl+Shift+R(to bypass cache) - Navigate to any page with checkboxes (e.g. product form, role permissions, POS settings)
- Tap a checkbox — it should toggle on and off
Always hard-refresh (Ctrl+Shift+R) after making changes to app.js to ensure the browser loads the updated file.
Affected Versions​
- Ultimate POS: v6.x (all versions using iCheck)
- Affected tablets: iPadOS 13+ (2019+), Windows tablets, some Android tablets with desktop-mode user agents
- Not affected: Desktop browsers, older iPads (iOS 12 and below), phones
💛 Support this project