Describe the bug
The Globals interface in storybook/internal/csf is used for two structurally different purposes that require different types:
- Story/meta-level globals setting — a partial override that pins specific toolbar controls. A story only sets the fields it cares about:
export const MyStory: StoryObj = {
globals: { vertical: INSURANCE_CATEGORY.DENTAL }, // partial — intentional
};
- Runtime
context.globals — the resolved globals object inside a render function or decorator, where all fields are present because defaults are set at the preview level:
(Story, context) => {
context.globals.locale // should be ACCOUNT_AREA_LOCALE, not ACCOUNT_AREA_LOCALE | undefined
}
Because both use Globals, any project that augments it with typed fields faces a forced choice:
- Make augmented fields optional →
context.globals.myField is MY_FIELD_TYPE | undefined even though defaults are always set, requiring boilerplate defensive code or casts everywhere reads happen.
- Make augmented fields required → every story that does a partial
globals: { vertical: ... } override gets a TS error for missing fields (and setting missing fields blocks them from being changed by the story user).
Neither is correct. The type system offers no way to express "partial when writing, resolved when reading."
Suggested fix
Introduce a distinction in the type signatures:
// In StoryAnnotations / ComponentAnnotations
globals?: Partial<TGlobals>; // partial override
// In StoryContext
globals: TGlobals; // fully resolved
CSF4's definePreview / defineStory typed chain could naturally carry this distinction, but the underlying Globals interface and StoryContext should reflect it regardless of whether a project uses CSF3 or CSF4.
Reproduction link
https://codesandbox.io/p/sandbox/storybook-globals-repro-53hmv7
Reproduction steps
Add your custom global variable myField: MY_FILED_TYPE, and try to implement TS types that make each of your stories properly typed.
Workaround currently required:
Projects must maintain two separate interfaces manually:
// For context reads — non-optional, project-defined
interface StorybookGlobals {
myField: MY_FILED_TYPE;
}
// For story writes — keep Globals augmentation optional
declare module 'storybook/internal/csf' {
interface Globals {
myField?: MY_FILED_TYPE; // optional to allow partial story overrides
}
}
// Then cast useGlobals() at the read site
const [, updateGlobals] = useGlobals() as [unknown, (g: Partial<StorybookGlobals>) => void];
const globals = getStorybookGlobals(context); // custom helper that fills defaults
This duplication is error-prone and should not be necessary.
Expected behaviour
Story-level globals should be typed as Partial<Globals> (or a dedicated GlobalsOverride type), the same way story args are a partial of the component's full args type. context.globals should be typed as Globals (non-optional fields), reflecting that the preview has resolved all defaults before the render function is called.
This mirrors a pattern Storybook already gets right for args: Meta.args accepts Partial<TArgs>, while context.args in a render function gives the full resolved TArgs.
System
System:
OS: macOS 26.5
CPU: (14) arm64 Apple M4 Pro
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.14.1 - ~/.volta/tools/image/node/24.14.1/bin/node
npm: 11.11.0 - ~/.volta/tools/image/node/24.14.1/bin/npm <----- active
Browsers:
Chrome: 148.0.7778.216
Safari: 26.5
npmPackages:
@storybook/addon-a11y: 9.1.20 => 9.1.20
@storybook/addon-docs: 9.1.20 => 9.1.20
@storybook/addon-onboarding: 9.1.20 => 9.1.20
@storybook/react-vite: 9.1.20 => 9.1.20
eslint-plugin-storybook: 9.1.20 => 9.1.20
msw-storybook-addon: 2.0.5 => 2.0.5
storybook: 9.1.20 => 9.1.20
Additional context
No response
Describe the bug
The Globals interface in
storybook/internal/csfis used for two structurally different purposes that require different types:context.globals— the resolved globals object inside a render function or decorator, where all fields are present because defaults are set at the preview level:Because both use
Globals, any project that augments it with typed fields faces a forced choice:context.globals.myFieldisMY_FIELD_TYPE | undefinedeven though defaults are always set, requiring boilerplate defensive code or casts everywhere reads happen.globals: { vertical: ... } overridegets aTSerror for missing fields (and setting missing fields blocks them from being changed by the story user).Neither is correct. The type system offers no way to express "partial when writing, resolved when reading."
Suggested fix
Introduce a distinction in the type signatures:
CSF4'sdefinePreview/defineStorytyped chain could naturally carry this distinction, but the underlyingGlobalsinterface andStoryContextshould reflect it regardless of whether a project usesCSF3orCSF4.Reproduction link
https://codesandbox.io/p/sandbox/storybook-globals-repro-53hmv7
Reproduction steps
Add your custom global variable
myField: MY_FILED_TYPE, and try to implementTStypes that make each of your stories properly typed.Workaround currently required:
Projects must maintain two separate interfaces manually:
This duplication is error-prone and should not be necessary.
Expected behaviour
Story-level globals should be typed as
Partial<Globals>(or a dedicatedGlobalsOverridetype), the same way story args are a partial of the component's full args type.context.globalsshould be typed asGlobals(non-optional fields), reflecting that the preview has resolved all defaults before the render function is called.This mirrors a pattern Storybook already gets right for args:
Meta.argsacceptsPartial<TArgs>, whilecontext.argsin a render functiongivesthe full resolvedTArgs.System
System: OS: macOS 26.5 CPU: (14) arm64 Apple M4 Pro Shell: 5.9 - /bin/zsh Binaries: Node: 24.14.1 - ~/.volta/tools/image/node/24.14.1/bin/node npm: 11.11.0 - ~/.volta/tools/image/node/24.14.1/bin/npm <----- active Browsers: Chrome: 148.0.7778.216 Safari: 26.5 npmPackages: @storybook/addon-a11y: 9.1.20 => 9.1.20 @storybook/addon-docs: 9.1.20 => 9.1.20 @storybook/addon-onboarding: 9.1.20 => 9.1.20 @storybook/react-vite: 9.1.20 => 9.1.20 eslint-plugin-storybook: 9.1.20 => 9.1.20 msw-storybook-addon: 2.0.5 => 2.0.5 storybook: 9.1.20 => 9.1.20Additional context
No response