How to Use Project Annotations in Storybook: setProjectAnnotations and setDefaultProjectAnnotations Explained
Call setProjectAnnotations once in your test setup file to register global decorators and parameters from your .storybook/preview configuration, while framework packages automatically register their defaults via setDefaultProjectAnnotations to ensure rendering logic is always available.
Project annotations in Storybook represent the global configuration—decorators, parameters, and global types—that apply to every story in your project. When testing stories outside the Storybook UI using utilities like composeStory, you must manually wire these globals using the setProjectAnnotations API from the storybookjs/storybook repository.
What Are Project Annotations in Storybook?
Project annotations are the global settings defined in your .storybook/preview.ts (or .js) file. They include:
- Global decorators that wrap every story (e.g., theming providers, accessibility checkers)
- Global parameters for addon configuration (e.g., viewport settings, backgrounds)
- Global types for toolbar controls that persist across stories
- Initial globals for default state values
When running stories inside the Storybook UI, these annotations are loaded automatically. However, when composing stories for unit testing with composeStory or composeStories, you must explicitly register them using the portable stories API.
setDefaultProjectAnnotations vs setProjectAnnotations: Key Differences
Storybook provides two distinct functions for registering project annotations, each serving a different purpose in the configuration hierarchy.
setDefaultProjectAnnotations (Framework Defaults)
The setDefaultProjectAnnotations function stores baseline annotations that framework packages (like Vue, React, or Svelte) require to function. According to the source code in code/core/src/preview-api/modules/store/csf/portable-stories.ts, this function assigns values to globalThis.defaultProjectAnnotations.
Framework wrappers call this internally to ensure their rendering logic—such as Vue’s renderToCanvas implementation—is always available before user configuration is applied. You typically do not call this function directly unless you are building a custom renderer.
setProjectAnnotations (User Configuration)
The setProjectAnnotations function merges your project’s preview file configuration with the core and default annotations. As implemented in portable-stories.ts, this function composes three layers:
- Core annotations from
getCoreAnnotations()(Storybook’s internal defaults) - Default annotations from
globalThis.defaultProjectAnnotations(framework-specific baseline) - User annotations passed as the function argument
The merged result is stored in globalThis.globalProjectAnnotations, which composeStory and composeStories reference automatically when no explicit annotations are provided.
How Project Annotations Are Merged Internally
Understanding the merge strategy helps debug configuration issues. The internal implementation in code/core/src/preview-api/modules/store/csf/portable-stories.ts uses composeConfigs to layer configurations:
globalThis.globalProjectAnnotations = composeConfigs([
...getCoreAnnotations(), // Storybook’s internal defaults
globalThis.defaultProjectAnnotations ?? {}, // Framework defaults (Vue, React, etc.)
composeConfigs(annotations.map(extractAnnotation)), // User-provided preview config
]);
When composeStory executes, it normalizes the final configuration by merging the global project annotations with any story-specific overrides:
const normalizedProjectAnnotations = normalizeProjectAnnotations(
composeConfigs([
defaultConfig ?? globalThis.globalProjectAnnotations ?? {}, // Global fallback
projectAnnotations ?? {}, // Explicit override for this composition
])
);
This hierarchy ensures that framework essentials are never overwritten accidentally, while user configurations take precedence for global settings.
Practical Implementation Examples
Setting Up Project Annotations for Vue 3
Framework packages like @storybook/vue3 automatically register their defaults before your configuration. In your test setup file:
// .storybook/vitest.setup.ts
import { setProjectAnnotations } from '@storybook/vue3';
import * as projectAnnotations from './preview'; // Your .storybook/preview.ts
setProjectAnnotations(projectAnnotations);
The Vue wrapper in code/renderers/vue3/src/portable-stories.ts internally calls setDefaultProjectAnnotations with Vue-specific rendering logic before executing your setProjectAnnotations call.
Configuring React Project Annotations
The pattern is identical for React. The setup file registers your preview configuration:
// vitest.setup.ts
import { setProjectAnnotations } from '@storybook/react';
import * as projectAnnotations from './.storybook/preview';
setProjectAnnotations(projectAnnotations);
React’s wrapper in code/renderers/react/src/portable-stories.ts handles the framework defaults automatically.
Using composeStory with Global Annotations
Once setProjectAnnotations is called in your setup file, individual tests require no additional configuration:
import { render } from '@testing-library/vue';
import { composeStory } from '@storybook/vue3';
import Meta, { Primary as PrimaryStory } from './Button.stories';
const Primary = composeStory(PrimaryStory, Meta); // Automatically uses global annotations
test('renders primary button', async () => {
const { getByText } = render(Primary, { props: { label: 'Hello world' } });
expect(getByText(/Hello world/i)).not.toBeNull();
});
The composeStory function automatically references globalThis.globalProjectAnnotations when no explicit project annotations argument is provided.
Overriding Annotations for Individual Tests
You can override global settings for specific test suites by passing annotations as the second argument to composeStories:
import { composeStories } from '@storybook/react';
import * as stories from './Card.stories';
const customAnnotations = {
decorators: [(Story) => <div className="test-wrapper"><Story/></div>],
};
const { Primary } = composeStories(stories, customAnnotations);
// customAnnotations are merged on top of global project annotations
This pattern is useful when you need test-specific wrappers that should not pollute the global configuration.
Where Project Annotations Live in the Source Code
The implementation spans the core preview API and framework-specific wrappers:
code/core/src/preview-api/modules/store/csf/portable-stories.ts– Contains the coresetDefaultProjectAnnotationsandsetProjectAnnotationsimplementations, including thecomposeConfigsmerge logic.code/renderers/vue3/src/portable-stories.ts– Vue 3 wrapper that registers framework-specific defaults viasetDefaultProjectAnnotationsbefore re-exporting the core API.code/renderers/react/src/portable-stories.ts– React wrapper following the same pattern for React-specific rendering logic.test-storybooks/react/.storybook/vitest.setup.ts– Real-world example demonstrating the standard setup pattern in a test environment.
Summary
- Project annotations are global configurations (decorators, parameters, globals) defined in
.storybook/previewthat must be registered when testing stories outside Storybook. setDefaultProjectAnnotationsis used internally by framework packages (Vue, React, Svelte) to register renderer-specific essentials likerenderToCanvas.setProjectAnnotationsmerges your preview file configuration with core and default annotations, storing the result inglobalThis.globalProjectAnnotations.- Call
setProjectAnnotationsonce in a global test setup file (e.g.,vitest.setup.ts) to enablecomposeStoryandcomposeStoriesto automatically apply your global configuration. - Override globally by passing annotation objects directly to
composeStoryorcomposeStorieswhen you need test-specific behavior.
Frequently Asked Questions
What is the difference between setProjectAnnotations and setDefaultProjectAnnotations?
setDefaultProjectAnnotations registers baseline annotations that framework packages require to function, such as Vue’s renderToCanvas implementation, and is called automatically by framework wrappers. setProjectAnnotations is the public API you call in your test setup to register your .storybook/preview configuration, which then merges with the defaults.
Where should I call setProjectAnnotations in my test setup?
Call setProjectAnnotations once in a global setup file such as .storybook/vitest.setup.ts, vitest.setup.ts, or jest.setup.js, importing your project annotations from .storybook/preview. This ensures that composeStory and composeStories automatically access your global decorators and parameters without requiring manual passing in every test file.
Can I override global project annotations for a single story?
Yes, you can override global annotations for individual stories or test suites by passing a custom annotations object as the second argument to composeStory or composeStories. These overrides are merged on top of the global configuration stored in globalThis.globalProjectAnnotations, allowing test-specific wrappers or parameters without affecting the global state.
How do framework-specific annotations get registered automatically?
Framework packages like @storybook/vue3 and @storybook/react wrap the core setProjectAnnotations function and internally call setDefaultProjectAnnotations with their renderer-specific configuration before executing your call. This happens in files like code/renderers/vue3/src/portable-stories.ts, ensuring essential rendering logic is present before your user-defined preview configuration is merged.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →