# Feature Flags

> 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.

- Skill: `primer/feature-flags` (Agent Skill)
- Install (CLI): `npx skillmds@latest add primer/feature-flags`
- Raw SKILL.md: https://api.skillmd.com/api/skills/primer/feature-flags/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: primer (https://skillmd.com/u/primer)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/primer/feature-flags

---


# 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`:

```tsx
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`:

```typescript
export const DefaultFeatureFlags = FeatureFlagScope.create({
  // ...existing flags...
  primer_react_my_new_feature: false,
})
```

### 2. Use `useFeatureFlag` in your component

```tsx
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

```tsx
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

```tsx
function ExampleComponent(props) {
  const enabled = useFeatureFlag('primer_react_my_feature')
  if (enabled) {
    return <ExampleComponentNext {...props} />
  }
  return <ExampleComponentClassic {...props} />
}
```

### Data attributes on DOM elements

```tsx
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`)

```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`)

```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:

```tsx
import {FeatureFlags} from '../../FeatureFlags'

// Test with flag enabled
render(
  <FeatureFlags flags={{primer_react_my_feature: true}}>
    <MyComponent />
  </FeatureFlags>,
)

// Test with flag disabled (or omit the wrapper to use default values from DefaultFeatureFlags)
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

```tsx
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

1. **Create** — Register the flag in `DefaultFeatureFlags.ts` with value `false`
2. **Implement** — Use `useFeatureFlag()` in components, write tests for both states
3. **Test in Storybook** — Create stories with the flag enabled/disabled
4. **Ship** — Merge to main; the flag remains off by default
5. **Enable at GitHub** — Use DevPortal to enable for specific actors, then staffship, then GA
6. **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.

