Build a Hold-to-Toggle Contact on a VRChat Avatar

Create an avatar control that changes state only after a hand or finger remains over a target for a set time. A reliable setup uses a VRC Contact Receiver for the live touch signal, an Animator state as the hold timer, and an Avatar Parameter Driver to latch the final on/off value.

Recommended structure

Keep the temporary contact signal local and synchronize only the final feature state.

  1. Add a Constant Contact Receiver that detects a hand or index finger.
  2. Build cancellable Hold On and Hold Off timer states in the FX controller.
  3. Drive one Boolean output parameter and provide an Expressions Menu fallback.
Use two parameters

HoldContact is a temporary local Animator input from the receiver. FeatureEnabled is the latched result. Add only the result to Expression Parameters when it needs saving, synchronization, or a menu control.

Video Companion

Creator: Strange Petals
Video: Hold Toggles Tutorial

How the Hold Toggle Works

Hand or finger enters receiver
        ↓
HoldContact = true
        ↓
Animator enters a timed hold state
        ↓
Contact leaves early? ── yes ──→ Cancel and return
        ↓ no
Timer completes
        ↓
Avatar Parameter Driver changes FeatureEnabled
        ↓
Wait for the hand to leave before arming the opposite toggle

The release gate prevents one long touch from turning the feature on and immediately starting the turn-off timer.

Parameter Plan

Parameter Type Location Purpose
HoldContact Bool FX Animator only True while a matching contact remains inside the receiver
FeatureEnabled Bool FX Animator and Expression Parameters Stores the final off/on state

Configure FeatureEnabled according to the result:

  • enable Synced when other players should see the feature;
  • enable Saved when the user should keep the choice after changing worlds or avatars;
  • leave Saved disabled when the feature should return to its default whenever the avatar reloads.

A synchronized Boolean uses one bit of expression-parameter memory. The temporary receiver input does not need to be added to Expression Parameters.

Add the Contact Receiver

Create a child object at the target area and add VRC Contact Receiver.

Avatar
└── Armature
    └── TargetBone
        └── HoldToggleReceiver
            └── VRC Contact Receiver

Recommended receiver setup for a wearer-controlled toggle:

Receiver setting Value Reason
Shape Type Sphere, capsule, or box Match the visible target area
Collision Tag Hand or FingerIndex Detect the built-in hand or index-finger senders
Allow Self Enabled Let the wearer's body activate the receiver
Allow Others Disabled Prevent another avatar from changing the wearer's setting
Local Only Enabled Keep the receiver evaluation local when only the wearer needs it
Receiver Type Constant Keep the parameter true while contact remains present
Parameter HoldContact Feed the FX Animator input

Built-in body-part Contact Senders are generated for humanoid avatars when the avatar author has not replaced them. Tags are case-sensitive, so Hand and FingerIndex must use the documented capitalization.

Other-player interactions

If the feature intentionally allows another player to activate it, enable Allow Others and test the privacy and consent behavior in VRChat. Do not enable it accidentally on clothing removal, visibility, or other personal controls.

Size and Place the Receiver

Place the receiver where the user can reach it naturally without touching it during ordinary movement.

Good target areas:

  • a badge, wrist control, or shoulder panel;
  • a clearly marked prop control;
  • an area separated from PhysBones and frequently touched accessories.

Avoid:

  • overlapping the receiver with the avatar's resting hand position;
  • making it so large that normal gestures start the timer;
  • stacking several receivers in the same space without distinct tags or indicators.

Use a visible icon, material, or object so the wearer knows where to hold. The visible target does not have to match the receiver exactly, but it should communicate its usable area.

Create the Hold Timer Clip

Create a small animation clip whose duration equals the intended hold time. It may be empty, or it can animate a progress ring or indicator.

For example:

HoldTimer.anim
Duration: chosen hold interval
Motion: optional progress indicator from empty to full

Use the clip in both timer states. The transition that completes a successful hold uses Has Exit Time with an exit time of 1, so it fires after one full pass through the timer clip.

The early-cancel transition should not use an exit time. It must return immediately when HoldContact becomes false.

Build a Release-Gated State Machine

Use six logic states:

Entry
├── FeatureEnabled = false → Off Ready
└── FeatureEnabled = true  → On Ready

Off Ready
└── HoldContact = true → Hold to Turn On
    ├── HoldContact = false → Off Ready
    └── Exit Time 1 → On, Wait for Release
        └── HoldContact = false → On Ready

On Ready
└── HoldContact = true → Hold to Turn Off
    ├── HoldContact = false → On Ready
    └── Exit Time 1 → Off, Wait for Release
        └── HoldContact = false → Off Ready

The Entry branches are important when FeatureEnabled is saved or changed through the Expressions Menu. The state machine must begin on the branch that matches the current parameter value.

Configure the Transitions

Transition Condition Exit Time Transition duration
Off Ready → Hold to Turn On HoldContact is true Off Short or zero
Hold to Turn On → Off Ready HoldContact is false Off Short or zero
Hold to Turn On → On, Wait for Release None On, exit time 1 Short or zero
On, Wait for Release → On Ready HoldContact is false Off Short or zero
On Ready → Hold to Turn Off HoldContact is true Off Short or zero
Hold to Turn Off → On Ready HoldContact is false Off Short or zero
Hold to Turn Off → Off, Wait for Release None On, exit time 1 Short or zero
Off, Wait for Release → Off Ready HoldContact is false Off Short or zero

Give the early-cancel path priority over the successful timer completion. Test a release just before the timer ends to confirm the output does not change.

Latch the Output with Avatar Parameter Driver

Add an Avatar Parameter Driver to each successful wait-for-release state:

State Driver operation
On, Wait for Release Set FeatureEnabled to true
Off, Wait for Release Set FeatureEnabled to false

VRChat runs Avatar Parameter Driver operations when the Animator enters the state. Keep each destination state and transition alive long enough for the state behavior to execute; VRChat recommends at least 0.02 seconds of total state and transition time when very short timing could otherwise skip a behavior.

Do not use a Trigger-type parameter to store the result. VRChat recommends Boolean, Integer, or Float parameters for avatar state because Trigger parameters can become desynchronized.

Animate the Feature on a Separate Layer

Use FeatureEnabled on a second FX layer to control the actual object, material, blendshape, or animation:

Feature Output Layer
├── Feature Off
│   └── FeatureEnabled = true → Feature On
└── Feature On
    └── FeatureEnabled = false → Feature Off

Separating the timer logic from the visible result makes the system easier to debug and lets an Expressions Menu toggle reuse the same output parameter.

Animate only the properties required by the feature. Keep both states explicit so the off state restores every property changed by the on state.

Add Feedback During the Hold

A hold control needs feedback before it commits. Use one or more of:

  • a progress ring driven by the timer clip;
  • a material emission change while HoldContact is true;
  • a short completion animation when the driver changes the output;
  • separate icons for off, holding, and on.

Avoid relying only on sound or color. The wearer should be able to understand the state in a mirror and with audio muted.

Add an Expressions Menu Fallback

Add a Toggle control that writes the same FeatureEnabled Boolean. This gives Desktop users and anyone who cannot reach or position the contact reliably another way to operate the feature.

The menu control and hold logic remain synchronized because both change the same final parameter. The Entry and ready-state branching must always respect the current value rather than assuming the contact was the last control used.

Test in Unity and VRChat

VRChat's avatar components can run in Unity Play mode. Assign the relevant Animator Controller to the avatar's Animator before entering Play mode so its parameters update during component testing.

Then test an uploaded avatar in VRChat with the Avatar Overlay enabled:

Test Expected result
Touch leaves before timer completes Feature remains unchanged
Touch remains for the full interval Feature changes once
Hand stays inside after completion Opposite timer does not begin
Hand leaves and returns Opposite timer can begin
Expressions Menu changes the value Logic returns to the matching ready branch
Avatar reloads with Saved enabled Previous feature value returns
Remote player observes a Synced output Final feature state is visible remotely
Desktop user opens the menu fallback Feature remains operable without a hand contact
Help! The timer starts as soon as the avatar loads.

Move or resize the receiver so it does not overlap a resting hand or finger. Confirm the collision tag is specific enough for the intended body part.

Help! The parameter turns off when my hand leaves.

The Contact Receiver should drive the temporary HoldContact parameter. Use Avatar Parameter Driver to set a separate FeatureEnabled parameter after the timer completes.

Help! One long touch turns the feature on and then off.

Add the wait-for-release states. After a successful hold, do not arm the opposite timer until HoldContact has returned to false.

Help! Releasing just before completion still toggles the feature.

Check the transition order and interruption behavior. The immediate cancel transition conditioned on HoldContact = false must win before the exit-time transition commits the result.

Help! Other players cannot see the final state.

Add FeatureEnabled to Expression Parameters and enable Synced. Keep the temporary contact parameter local; only the latched result needs synchronization.

Help! The contact does not respond to my hand.

Confirm Allow Self is enabled and the receiver uses a matching, case-sensitive built-in tag such as Hand or FingerIndex. Use the Avatar Overlay in VRChat to inspect the contact volumes.

Help! The saved value loads into the wrong logic state.

Branch from Entry using the current FeatureEnabled value. A saved or menu-controlled parameter may already be true before the contact layer begins evaluating.

Official References

Related Guides

Topics: VRChat avatars, Avatar Dynamics, contacts, Animator, expression parameters