diff --git a/src/main/java/chylex/bettercontrols/input/ToggleTracker.java b/src/main/java/chylex/bettercontrols/input/ToggleTracker.java
new file mode 100644
index 0000000..3ef492a
--- /dev/null
+++ b/src/main/java/chylex/bettercontrols/input/ToggleTracker.java
@@ -0,0 +1,83 @@
+package chylex.bettercontrols.input;
+import net.minecraft.client.options.KeyBinding;
+
+public class ToggleTracker{
+	protected final KeyBinding bindingToggle;
+	protected final KeyBinding bindingReset;
+	
+	protected boolean isToggled;
+	
+	private boolean waitForRelease;
+	private boolean hasToggledWhileHoldingReset;
+	private boolean skipNextToggle;
+	
+	public ToggleTracker(final KeyBinding bindingToggle, final KeyBinding bindingReset){
+		this.bindingToggle = bindingToggle;
+		this.bindingReset = bindingReset;
+	}
+	
+	/*
+	 * Assume holding CTRL is used to sprint (reset key) and either G or CTRL + G is used to toggle sprint on.
+	 * The toggle modifier actually does not matter, having it just prevents trigger if the key is pressed alone.
+	 *
+	 * Pressing the toggle key alone switches the toggled state, then the key has to be released.
+	 * Holding the reset key while toggled will reset the toggle, but continue the sprint until released.
+	 *
+	 * Pressing the toggle key while holding reset key switches the toggled state, but allows both keys to be
+	 * released without interrupting the toggle (hasToggledWhileHoldingReset).
+	 *
+	 * Note that holding the reset key and pressing toggle key twice will switch the toggle twice, as expected.
+	 *
+	 * Holding the reset key while toggled, then pressing the toggle key again, will do nothing (skipNextToggle).
+	 * This allows resetting the toggle both by pressing the reset key alone, or with the full toggle key combo.
+	 * However, if the toggle key is then pressed again, it will function as usual.
+	 *
+	 * All of the logic combined allows for complex scenarios such as:
+	 *
+	 *   +CTRL, +G, -CTRL                 -->  toggled on
+	 *   +CTRL, +G, -CTRL, +CTRL          -->  toggled off (quick reset)
+	 *   +CTRL, +G, -CTRL, +CTRL, +G      -->  toggled off (full combo)
+	 *   +CTRL, +G, -CTRL, +CTRL, +G, +G  -->  toggled on
+	 */
+	
+	public boolean tick(){
+		final boolean isHoldingReset = isResetKeyPressed();
+		
+		if (bindingToggle.isPressed()){
+			if (!waitForRelease){
+				if (skipNextToggle){
+					skipNextToggle = false;
+				}
+				else{
+					isToggled = !isToggled;
+				}
+				
+				waitForRelease = true;
+				hasToggledWhileHoldingReset = isHoldingReset;
+			}
+		}
+		else{
+			waitForRelease = false;
+		}
+		
+		if (isToggled){
+			if (hasToggledWhileHoldingReset && !isHoldingReset){
+				hasToggledWhileHoldingReset = false;
+			}
+			else if (!hasToggledWhileHoldingReset && isHoldingReset){
+				isToggled = false;
+				skipNextToggle = true;
+			}
+		}
+		
+		if (skipNextToggle && !isHoldingReset){
+			skipNextToggle = false;
+		}
+		
+		return isToggled;
+	}
+	
+	protected boolean isResetKeyPressed(){
+		return bindingReset.isPressed();
+	}
+}
diff --git a/src/main/java/chylex/bettercontrols/input/ToggleTrackerForStickyKey.java b/src/main/java/chylex/bettercontrols/input/ToggleTrackerForStickyKey.java
new file mode 100644
index 0000000..da3d3c8
--- /dev/null
+++ b/src/main/java/chylex/bettercontrols/input/ToggleTrackerForStickyKey.java
@@ -0,0 +1,35 @@
+package chylex.bettercontrols.input;
+import chylex.bettercontrols.mixin.AccessKeyBindingFields;
+import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
+import net.minecraft.client.options.KeyBinding;
+import java.util.HashSet;
+import java.util.Set;
+
+public final class ToggleTrackerForStickyKey extends ToggleTracker{
+	private static final Set<KeyBinding> enabledOverrides = new HashSet<>();
+	
+	public static boolean isOverrideEnabled(final KeyBinding binding){
+		return enabledOverrides.contains(binding);
+	}
+	
+	private final BooleanConsumer setToggleState;
+	
+	public ToggleTrackerForStickyKey(final KeyBinding bindingToggle, final KeyBinding bindingStickyReset, final BooleanConsumer setToggleState){
+		super(bindingToggle, bindingStickyReset);
+		this.setToggleState = setToggleState;
+		this.setToggleState.accept(false);
+		enabledOverrides.add(bindingStickyReset);
+	}
+	
+	@Override
+	public boolean tick(){
+		final boolean isToggled = super.tick();
+		setToggleState.accept(isToggled);
+		return isToggled;
+	}
+	
+	@Override
+	protected boolean isResetKeyPressed(){
+		return ((AccessKeyBindingFields)bindingReset).isPressedField();
+	}
+}
diff --git a/src/main/java/chylex/bettercontrols/mixin/AccessKeyBindingFields.java b/src/main/java/chylex/bettercontrols/mixin/AccessKeyBindingFields.java
index c1b4499..a8ca93e 100644
--- a/src/main/java/chylex/bettercontrols/mixin/AccessKeyBindingFields.java
+++ b/src/main/java/chylex/bettercontrols/mixin/AccessKeyBindingFields.java
@@ -10,4 +10,10 @@ public interface AccessKeyBindingFields{
 	static Map<String, Integer> getCategoryOrderMap(){
 		throw new AssertionError();
 	}
+	
+	@Accessor("pressed")
+	boolean isPressedField();
+	
+	@Accessor("pressed")
+	void setPressedField(boolean value);
 }
diff --git a/src/main/java/chylex/bettercontrols/mixin/HookStickyKeyBindingState.java b/src/main/java/chylex/bettercontrols/mixin/HookStickyKeyBindingState.java
new file mode 100644
index 0000000..331d8ef
--- /dev/null
+++ b/src/main/java/chylex/bettercontrols/mixin/HookStickyKeyBindingState.java
@@ -0,0 +1,35 @@
+package chylex.bettercontrols.mixin;
+import chylex.bettercontrols.input.ToggleTrackerForStickyKey;
+import net.minecraft.client.options.KeyBinding;
+import net.minecraft.client.options.StickyKeyBinding;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import java.util.function.BooleanSupplier;
+
+@Mixin(StickyKeyBinding.class)
+public abstract class HookStickyKeyBindingState extends KeyBinding{
+	@Shadow
+	@Final
+	private BooleanSupplier toggleGetter;
+	
+	public HookStickyKeyBindingState(final String translationKey, final int code, final String category){
+		super(translationKey, code, category);
+	}
+	
+	@Inject(method = "setPressed(Z)V", at = @At("HEAD"), cancellable = true)
+	public void setPressed(final boolean pressed, final CallbackInfo info){
+		if (ToggleTrackerForStickyKey.isOverrideEnabled(this)){
+			((AccessKeyBindingFields)this).setPressedField(pressed);
+			info.cancel();
+		}
+	}
+	
+	@Override
+	public boolean isPressed(){
+		return super.isPressed() || (ToggleTrackerForStickyKey.isOverrideEnabled(this) && toggleGetter.getAsBoolean());
+	}
+}
diff --git a/src/main/resources/mixins.json b/src/main/resources/mixins.json
index 70c7709..77d4e93 100644
--- a/src/main/resources/mixins.json
+++ b/src/main/resources/mixins.json
@@ -7,7 +7,8 @@
     "AccessKeyBindingFields",
     "AccessScreenButtons",
     "HookLoadGameOptions",
-    "HookOpenScreen"
+    "HookOpenScreen",
+    "HookStickyKeyBindingState"
   ],
   "injectors": {
     "defaultRequire": 1