Quick answer: Bind the action’s Triggered event for “sprint on” and Completed/Canceled for “sprint off.” Don’t derive sprint state from Triggered alone — modifier release isn’t a Triggered event. Disable Consume Input if multiple layers need to see the key.

Hold Shift to sprint. Release Shift. Character keeps sprinting. Enhanced Input’s state model is more nuanced than the legacy bool-press input system, and modifiers stick if you only handle the Triggered event.

The Symptom

A held key’s effect persists after the key is released. Sprint stays on. ADS stays on. Crouch toggles instead of holds. Common after switching from legacy input to Enhanced Input.

The Mental Model

Enhanced Input emits five trigger event types per action:

For a hold-to-act binding (sprint), you need both Triggered (act) and Completed/Canceled (stop acting).

The Fix

void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    UEnhancedInputComponent* EIC = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);

    EIC->BindAction(SprintAction, ETriggerEvent::Triggered, this, &APlayerCharacter::SprintStart);
    EIC->BindAction(SprintAction, ETriggerEvent::Completed, this, &APlayerCharacter::SprintStop);
    EIC->BindAction(SprintAction, ETriggerEvent::Canceled,  this, &APlayerCharacter::SprintStop);
}

void APlayerCharacter::SprintStart(const FInputActionValue& Value) { bIsSprinting = true; }
void APlayerCharacter::SprintStop(const FInputActionValue& Value)  { bIsSprinting = false; }

Sprint is now driven by both press (Triggered) and release (Completed) events. Canceled covers the edge case where Enhanced Input loses focus.

Toggle vs Hold

For toggle behavior, change the Trigger on the Input Action asset to Tap or Pulse and only bind Triggered. Toggle implementations need to track state in the handler:

void APlayerCharacter::CrouchToggle(const FInputActionValue& Value)
{
    bIsCrouching = !bIsCrouching;
}

Mapping Context Priority

If the player’s mapping context has Sprint and a higher-priority context (UI menu) also binds Shift, the higher one consumes the input and Sprint never sees release. Don’t bind keys redundantly across active contexts; or set Consume Input = false where appropriate.

Verifying

Print on each event:

UE_LOG(LogTemp, Log, TEXT("Sprint Started"));   // in SprintStart
UE_LOG(LogTemp, Log, TEXT("Sprint Stopped"));    // in SprintStop

Hold and release the key. Both events should print. If Stopped never prints, Completed/Canceled binding is missing.

“Triggered for press. Completed + Canceled for release. State stays clean.”

Related Issues

For Steam Input action set switching, see Steam Input. For input not detected, see input not firing.

Five events. Three to handle. Modifier releases.