Definition
The time it takes a user to hit a target scales with how far the cursor must travel and how small the target is. Big nearby targets win; tiny far ones lose. The law is mathematical, not aesthetic — every increase in target size or proximity is a real reduction in friction.
Why it matters for ShurIQ reports
Filter chips, viewport switchers, and stack-rank row actions live or die by hit-target geometry. On a brand-intelligence dashboard with dense data, a 36-pixel button next to a 12-pixel chart label means the executive will mis-tap and disengage. Fitts's Law is what justifies generous click areas around every interactive element on a viz hub.
Takeaways
- Make every primary action at least 44px on touch and 32px on cursor; never sacrifice this for visual elegance.
- Place destructive or low-confidence actions at greater distance and smaller size; never at thumb-reach next to the primary action.
- Pin frequent controls (viewport switcher, filter reset) to screen edges where the cursor cannot overshoot.
- Treat hover/touch areas as bigger than the visual button — invisible padding is a feature.
Visual motion language
On approach, interactive elements show a subtle magnetic-pull (1–2px shift toward cursor) and a scale-pulse on press. Snap-grid alignment ensures targets land where the eye expects.
Origins
Paul Fitts, 1954 — formalized as a movement-time equation in human-factors research.
Cavalry scene
The script below builds this concept's motion in Cavalry through the Stallion bridge. Pipe to cavalry_run_script via MCP, or paste into Cavalry's JavaScript Editor.
// Laws of UX · fittss-law · Cavalry scene
// Motion family: center-out radial pulse (target rings + traveling cursor)
// Palette: olive-green + cream
// To run: pipe to cavalry_run_script tool, or paste into Cavalry's JavaScript Editor
// Built 2026-04-30 by ShurAI
(function () {
var PREFIX = "claude_lawofux_fittss-law_";
var existing = api.getAllSceneLayers();
for (var i = 0; i < existing.length; i++) {
try {
var nm = api.getNiceName(existing[i]);
if (nm && nm.indexOf("claude_lawofux_") === 0) api.deleteLayer(existing[i]);
} catch (e) {}
}
var BG = "#4A5A2C";
var CREAM = "#D6CDB0";
var bg = api.primitive("rectangle", PREFIX + "bg");
api.set(bg, { "generator.dimensions": [1080, 1080] });
api.setFill(bg, true);
api.set(bg, { "material.materialColor": BG });
// 4 concentric ring strokes
var radii = [240, 170, 110, 50];
for (var i = 0; i < radii.length; i++) {
var ring = api.primitive("ellipse", PREFIX + "ring_" + i);
api.set(ring, {
"generator.radius": [radii[i], radii[i]],
"position.x": 0, "position.y": 0,
"opacity": 0
});
api.setFill(ring, false);
api.setStroke(ring, true);
api.set(ring, {
"stroke.strokeColor": CREAM,
"stroke.width": 12
});
api.keyframe(ring, i * 4, { "opacity": 0 });
api.keyframe(ring, i * 4 + 12, { "opacity": 100 });
}
// Bullseye dot
var bull = api.primitive("ellipse", PREFIX + "bullseye");
api.set(bull, {
"generator.radius": [12, 12],
"position.x": 0, "position.y": 0,
"opacity": 0
});
api.setFill(bull, true);
api.set(bull, { "material.materialColor": CREAM });
api.keyframe(bull, 16, { "opacity": 0 });
api.keyframe(bull, 28, { "opacity": 100 });
// Traveling cursor blob from off-canvas top-right to bullseye
var cursor = api.primitive("ellipse", PREFIX + "cursor");
api.set(cursor, {
"generator.radius": [30, 30],
"position.x": 380, "position.y": 380,
"opacity": 0
});
api.setFill(cursor, true);
api.set(cursor, { "material.materialColor": CREAM });
api.keyframe(cursor, 30, { "opacity": 0, "position.x": 380, "position.y": 380, "scale.x": 1.0, "scale.y": 1.0 });
api.keyframe(cursor, 36, { "opacity": 40 });
api.keyframe(cursor, 60, { "opacity": 60, "position.x": 0, "position.y": 0, "scale.x": 0.7, "scale.y": 0.7 });
api.keyframe(cursor, 72, { "opacity": 0 });
api.magicEasing(cursor, "position.x", 60, "EaseOut", "");
api.magicEasing(cursor, "position.y", 60, "EaseOut", "");
var layerCount = api.getAllSceneLayers().length;
console.log("scene built: fittss-law (" + layerCount + " layers)");
})();