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.
Keep the temporary contact signal local and synchronize only the final feature state.
- Add a Constant Contact Receiver that detects a hand or index finger.
- Build cancellable Hold On and Hold Off timer states in the FX controller.
- Drive one Boolean output parameter and provide an Expressions Menu fallback.
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.
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
HoldContactis 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
- VRChat Contacts
- Built-In Contact Tags
- Animator Parameters
- Avatar Parameter Driver
- Expressions Menu and Controls
- Debugging Avatar Components
- Unity animation transitions
Related Guides
- Avatar Dynamics Testing
- Avatar Dynamics Interaction Senders and Receivers
- Avatar Creation
- Avatar Optimization Checklist
- Setting Up VRChat Creator Companion
- VRChat Documentation
Topics: VRChat avatars, Avatar Dynamics, contacts, Animator, expression parameters