| name | feature-flags |
| description | Use when: implementing, creating, testing, or debugging feature flags in Primer React. Covers useFeatureFlag hook, FeatureFlags provider, DefaultFeatureFlags, FeatureFlagScope, Storybook integration, and the feature flag lifecycle at GitHub. |
Feature Flags in Primer React
Feature flags provide a way to incrementally build and deliver changes in Primer alongside the feature flag system at GitHub. They help build confidence in changes and improve the reliability of releases.
Architecture
Feature flags are implemented in packages/react/src/FeatureFlags/ with these core pieces:
| File | Purpose |
|---|
FeatureFlags.tsx | React context provider component |
useFeatureFlag.ts | Hook to check if a flag is enabled |
FeatureFlagScope.ts | Class that manages flag collections and merging |
DefaultFeatureFlags.ts | Default flag values (all flags listed here) |
FeatureFlagContext.ts | React context definition |
index.ts | Public exports |
Exports
Feature flags are exported from @primer/react/experimental:
import {FeatureFlags, useFeatureFlag, DefaultFeatureFlags} from '@primer/react/experimental'
They are NOT exported from the main @primer/react entry point.
How to Use a Feature Flag in a Component
1. Add the flag to DefaultFeatureFlags
Register your flag in packages/react/src/FeatureFlags/DefaultFeatureFlags.ts with a default value of false:
export const DefaultFeatureFlags = FeatureFlagScope.create({
primer_react_my_new_feature: false,
})
2. Use useFeatureFlag in your component
import {useFeatureFlag} from '../FeatureFlags'
function MyComponent() {
const enabled = useFeatureFlag('primer_react_my_new_feature')
if (enabled) {
return <NewBehavior />
}
return <CurrentBehavior />
}
3. Naming conventions
- Prefix with
primer_react_ for flags in the @primer/react package
- Use snake_case:
primer_react_my_feature_name
Patterns
Change behavior conditionally
function ExampleComponent({children}) {
const enabled = useFeatureFlag('primer_react_my_feature')
return (
<button
onClick={() => {
if (enabled) {
// new behavior
} else {
// current behavior
}
}}
>
{children}
</button>
)
}
Toggle between two component versions
function ExampleComponent(props) {
const enabled = useFeatureFlag('primer_react_my_feature')
if (enabled) {
return <ExampleComponentNext {...props} />
}
return <ExampleComponentClassic {...props} />
}
Data attributes on DOM elements
function MyOverlay() {
const enabled = useFeatureFlag('primer_react_my_feature')
return <div data-my-feature={enabled ? '' : undefined} />
}
Real Codebase Examples
CSS anchor positioning (packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx)
const cssAnchorPositioning = useFeatureFlag('primer_react_css_anchor_positioning')
useEffect(() => {
if (cssAnchorPositioning && !hasLoadedAnchorPositioningPolyfill.current) {
applyAnchorPositioningPolyfill()
hasLoadedAnchorPositioningPolyfill.current = true
}
}, [open, overlayRef, updateOverlayRef, cssAnchorPositioning])
Breadcrumbs overflow (packages/react/src/Breadcrumbs/Breadcrumbs.tsx)
const overflowMenuEnabled = useFeatureFlag('primer_react_breadcrumbs_overflow_menu')
Testing with Feature Flags
Wrap your component with the FeatureFlags provider to set flag values in tests:
import {FeatureFlags} from '../../FeatureFlags'
render(
<FeatureFlags flags={{primer_react_my_feature: true}}>
<MyComponent />
</FeatureFlags>,
)
render(
<FeatureFlags flags={{primer_react_my_feature: false}}>
<MyComponent />
</FeatureFlags>,
)
Key testing behaviors
- Unknown flags default to
false
- Nested
FeatureFlags providers merge flags — inner values override outer values
- You should test both enabled and disabled states
Storybook Integration
Enable flag in a specific story
import {FeatureFlags} from '../../FeatureFlags'
export const WithFeatureEnabled = () => (
<FeatureFlags flags={{primer_react_my_feature: true}}>
<MyComponent />
</FeatureFlags>
)
export const WithFeatureDisabled = () => <MyComponent />
Enable flag in all stories
Storybook's global preview (packages/react/.storybook/preview.jsx) already wraps all stories in a FeatureFlags provider, using DefaultFeatureFlags as the source of default values and toolbar options. In most cases you only need to register your flag in DefaultFeatureFlags.ts; you do not need to manually add it to the FeatureFlags wrapper.
To enable a flag globally via environment, add its exact flag name (for example, primer_react_my_feature) to the featureFlagEnvList set in preview.jsx, and set the corresponding env var to 1 (for example, VITE_primer_react_my_feature=1). The preview code reads import.meta.env[\VITE*${flag}`], so the part after VITE*` must match the flag string exactly.
Feature Flag Lifecycle
- Create — Register the flag in
DefaultFeatureFlags.ts with value false
- Implement — Use
useFeatureFlag() in components, write tests for both states
- Test in Storybook — Create stories with the flag enabled/disabled
- Ship — Merge to main; the flag remains off by default
- Enable at GitHub — Use DevPortal to enable for specific actors, then staffship, then GA
- Remove — Once fully rolled out, remove the flag from code and
DefaultFeatureFlags.ts
Temporarily testing in Dotcom CI
Set your flag to true in DefaultFeatureFlags.ts to enable it by default for CI testing. Set it back to false before merging.
FeatureFlags Provider Internals
FeatureFlagScope.create(flags) — Creates a scope from a plain object {[key: string]: boolean}
FeatureFlagScope.merge(a, b) — Merges two scopes; b values override a
- The
FeatureFlags component merges parent context flags with the provided flags prop
DefaultFeatureFlags is the initial context value — it defines all known flags
Current Feature Flags
Check packages/react/src/FeatureFlags/DefaultFeatureFlags.ts for the current list of all registered feature flags and their default values.