React Feature Flags
Flag Files
File Purpose
packages/shared/ReactFeatureFlags.js
Default flags (canary), EXPERIMENTAL overrides
packages/shared/forks/ReactFeatureFlags.www.js
www channel, VARIANT overrides
packages/shared/forks/ReactFeatureFlags.native-fb.js
React Native, VARIANT overrides
packages/shared/forks/ReactFeatureFlags.test-renderer.js
Test renderer
Gating Tests
@gate pragma (test-level)
Use when the feature is completely unavailable without the flag:
// @gate enableViewTransition it('supports view transitions', () => { // This test only runs when enableViewTransition is true // and is SKIPPED (not failed) when false });
gate() inline (assertion-level)
Use when the feature exists but behavior differs based on flag:
it('renders component', async () => { await act(() => root.render(<App />));
if (gate(flags => flags.enableNewBehavior)) { expect(container.textContent).toBe('new output'); } else { expect(container.textContent).toBe('legacy output'); } });
Adding a New Flag
-
Add to ReactFeatureFlags.js with default value
-
Add to each fork file (*.www.js , *.native-fb.js , etc.)
-
If it should vary in www/RN, set to VARIANT in the fork file
-
Gate tests with @gate flagName or inline gate()
Checking Flag States
Use /flags to view states across channels. See the flags skill for full command options.
VARIANT Flags (GKs)
Flags set to VARIANT simulate gatekeepers - tested twice (true and false):
/test www <pattern> # VARIANT = true /test www variant false <pattern> # VARIANT = false
Debugging Channel-Specific Failures
-
Run /flags --diff <channel1> <channel2> to compare values
-
Check @gate conditions - test may be gated to specific channels
-
Run /test <channel> <pattern> to isolate the failure
-
Verify flag exists in all fork files if newly added
Common Mistakes
-
Forgetting both variants - Always test www AND www variant false for VARIANT flags
-
Using @gate for behavior differences - Use inline gate() if both paths should run
-
Missing fork files - New flags must be added to ALL fork files, not just the main one
-
Wrong gate syntax - It's gate(flags => flags.name) , not gate('name')