Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1761296
Reapply "Vue: Make globals reactive in decorators"
Sidnioulz Mar 12, 2026
6bda49b
Merge branch 'next' into reapply-vue-globals-reactivity-fix
Sidnioulz Mar 13, 2026
b1725c6
Apply suggestions from code review
yannbf Mar 13, 2026
089e2b7
Merge branch 'next' into reapply-vue-globals-reactivity-fix
Sidnioulz Mar 16, 2026
b2f06b9
UI: Hide addon panel Drag on pages without a panel
Sidnioulz Mar 16, 2026
bbb8384
UI: Hide manifest tag for now
Sidnioulz Mar 16, 2026
01b1051
fix(a11y): replace native disabled with isDisabled and aria-disabled …
WioletaKolodziej Aug 23, 2025
cff53d2
Finalise Button aria-disabled changes
Sidnioulz Mar 16, 2026
92c9867
Adjust remaining tests using toBeDisabled
Sidnioulz Mar 17, 2026
585d934
Fix mistake in E2E test locator
Sidnioulz Mar 17, 2026
c22afbb
UI: Use correct selector for addon panel focus check
Sidnioulz Mar 16, 2026
8e90c6e
Merge branch 'next' into sidnioulz/10-3-fix-focus-in-addon-panel-root
Sidnioulz Mar 17, 2026
9e58af5
Merge branch 'next' into sidnioulz/issue-31678-aria-disabled-finish
Sidnioulz Mar 17, 2026
0d30f65
AGENTS.md: document that `next` is the base branch
kasperpeulen Mar 17, 2026
f1d7dea
Merge pull request #34181 from storybookjs/worktree-update-agents-md
kasperpeulen Mar 17, 2026
609f377
Merge branch 'next' into sidnioulz/issue-31678-aria-disabled-finish
Sidnioulz Mar 17, 2026
662b52f
Merge pull request #34116 from storybookjs/reapply-vue-globals-reacti…
Sidnioulz Mar 17, 2026
90d86a1
Merge pull request #34162 from storybookjs/sidnioulz/10-3-resize-tool…
Sidnioulz Mar 17, 2026
63a8d4a
Merge pull request #34165 from storybookjs/sidnioulz/10-3-hide-manife…
Sidnioulz Mar 17, 2026
b8449a4
Merge pull request #34166 from storybookjs/sidnioulz/issue-31678-aria…
Sidnioulz Mar 17, 2026
985382c
Merge pull request #34164 from storybookjs/sidnioulz/10-3-fix-focus-i…
Sidnioulz Mar 17, 2026
21e7cb3
Write changelog for 10.3.0-beta.2 [skip ci]
storybook-bot Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This file is the canonical instruction source for coding agents. Files like `CLA

Storybook is a large TypeScript monorepo. The git root is the repo root, the main code lives in `code/`, and build tooling lives in `scripts/`.

- **Base branch**: `next` (all PRs should target `next`, not `main`)
- **Node.js**: `22.21.1` (see `.nvmrc`)
- **Package Manager**: Yarn Berry
- **Task orchestration**: NX plus the custom `yarn task` runner
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.prerelease.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 10.3.0-beta.2

- UI: Hide addon panel Drag on pages without a panel - [#34162](https://github.com/storybookjs/storybook/pull/34162), thanks @Sidnioulz!
- UI: Hide manifest tag for now - [#34165](https://github.com/storybookjs/storybook/pull/34165), thanks @Sidnioulz!
- UI: Make disabled Buttons keyboard-focusable - [#34166](https://github.com/storybookjs/storybook/pull/34166), thanks @Sidnioulz!
- UI: Use correct selector for addon panel focus check - [#34164](https://github.com/storybookjs/storybook/pull/34164), thanks @Sidnioulz!
- Vue: Make globals reactive in decorators - [#34116](https://github.com/storybookjs/storybook/pull/34116), thanks @Sidnioulz!

## 10.3.0-beta.1

- Addon-Docs: Add React as optimizeDeps entry - [#34176](https://github.com/storybookjs/storybook/pull/34176), thanks @valentinpalkovic!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@ export const Default: Story = {};
export const Submitting: Story = {
play: async ({ args }) => {
const button = await screen.findByRole('button', { name: 'Submit' });
await expect(button).toBeDisabled();
await expect(button).toHaveAttribute('aria-disabled', 'true');

await userEvent.click(await screen.findByText('Design system'));
await expect(button).toBeDisabled();
await expect(button).toHaveAttribute('aria-disabled', 'true');

await userEvent.click(await screen.findByText('Functional testing'));
await userEvent.click(await screen.findByText('Accessibility testing'));
await userEvent.click(await screen.findByText('Visual testing'));
await expect(button).toBeDisabled();
await expect(button).toHaveAttribute('aria-disabled', 'true');

await userEvent.selectOptions(screen.getByRole('combobox'), ['We use it at work']);
await expect(button).not.toBeDisabled();
await expect(button).not.toHaveAttribute('aria-disabled', 'true');

await userEvent.click(button);

await waitFor(async () => {
await expect(button).toBeDisabled();
await expect(button).toHaveAttribute('aria-disabled', 'true');
await expect(args.onComplete).toHaveBeenCalledWith({
building: {
'application-ui': false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ TestFunctionTypes.test(
},
async ({ canvas }) => {
const button = canvas.getByText('Arg from story');
await expect(button).toBeEnabled();
await expect(button).not.toHaveAttribute('aria-disabled', 'true');
}
);

Expand All @@ -106,7 +106,7 @@ export const ExtendedStorySinglePlayExample = TestFunctionTypes.extend({
},
play: async ({ canvas }) => {
const button = canvas.getByText('Arg from extended story');
await expect(button).toBeEnabled();
await expect(button).not.toHaveAttribute('aria-disabled', 'true');
},
});

Expand All @@ -120,7 +120,7 @@ ExtendedStorySingleTestExample.test(
'this is a very long test name to explain that this story test should guarantee that the args have been extended correctly',
async ({ canvas }) => {
const button = canvas.getByText('Arg from extended story');
await expect(button).toBeEnabled();
await expect(button).not.toHaveAttribute('aria-disabled', 'true');
}
);

Expand Down
28 changes: 27 additions & 1 deletion code/core/src/components/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';

import { FaceHappyIcon } from '@storybook/icons';

import { fn } from 'storybook/test';
import { expect, fn } from 'storybook/test';
import { styled } from 'storybook/theming';

import preview from '../../../../../.storybook/preview';
Expand Down Expand Up @@ -291,6 +291,32 @@ export const Disabled = meta.story({
ariaLabel: false,
disabled: true,
children: 'Disabled Button',
onClick: fn(),
},
render: (args) => (
<Row>
<Button variant="solid" {...args}>
Disabled Button
</Button>
</Row>
),
play: async ({ args, canvas, step }) => {
const button = canvas.getByRole('button', { name: 'Disabled Button' });

await step('Disabled button should be aria-disabled', async () => {
expect(button).toHaveAttribute('aria-disabled', 'true');
});

await step('Disabled button should not be clickable', async () => {
button.click();
expect(args.onClick).not.toHaveBeenCalled();
});

await step('Disabled button should be focusable for accessibility', async () => {
const button = canvas.getByRole('button', { name: 'Disabled Button' });
button.focus();
expect(button).toHaveFocus();
});
},
});

Expand Down
13 changes: 7 additions & 6 deletions code/core/src/components/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,13 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
variant={variant}
size={size}
padding={padding}
disabled={disabled || readOnly}
$disabled={disabled || readOnly}
aria-disabled={disabled || readOnly ? 'true' : undefined}
readOnly={readOnly}
active={active}
animating={isAnimating}
animation={animation}
onClick={handleClick}
onClick={disabled || readOnly ? undefined : handleClick}
aria-label={!readOnly && ariaLabel !== false ? ariaLabel : undefined}
aria-keyshortcuts={readOnly ? undefined : shortcutAttribute}
{...(readOnly ? {} : ariaDescriptionAttrs)}
Expand All @@ -165,7 +166,7 @@ const StyledButton = styled('button', {
padding?: 'small' | 'medium' | 'none';
variant?: 'outline' | 'solid' | 'ghost';
active?: boolean;
disabled?: boolean;
$disabled?: boolean;
readOnly?: boolean;
animating?: boolean;
animation?: 'none' | 'rotate360' | 'glow' | 'jiggle';
Expand All @@ -174,15 +175,15 @@ const StyledButton = styled('button', {
theme,
variant,
size,
disabled,
$disabled,
readOnly,
active,
animating,
animation = 'none',
padding,
}) => ({
border: 0,
cursor: readOnly ? 'inherit' : disabled ? 'not-allowed' : 'pointer',
cursor: readOnly ? 'inherit' : $disabled ? 'not-allowed' : 'pointer',
display: 'inline-flex',
gap: '6px',
alignItems: 'center',
Expand Down Expand Up @@ -216,7 +217,7 @@ const StyledButton = styled('button', {
verticalAlign: 'top',
whiteSpace: 'nowrap',
userSelect: 'none',
opacity: disabled && !readOnly ? 0.5 : 1,
opacity: $disabled && !readOnly ? 0.5 : 1,
margin: 0,
fontSize: `${theme.typography.size.s1}px`,
fontWeight: theme.typography.weight.bold,
Expand Down
13 changes: 7 additions & 6 deletions code/core/src/manager-api/modules/shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import { focusableUIElements } from './layout';

const { navigator, document } = global;

function wasFocusInElement(element: HTMLElement | null) {
return document.activeElement && element?.contains(document.activeElement);
}

export const isMacLike = () =>
navigator && navigator.platform ? !!navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) : false;
export const controlOrMetaKey = () => (isMacLike() ? 'meta' : 'control');
Expand Down Expand Up @@ -359,13 +363,11 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => {

case 'togglePanel': {
const wasPanelShown = fullAPI.getIsPanelShown();
const panelElement = document.getElementById(focusableUIElements.storyPanelRoot);
const wasFocusInPanel =
panelElement && document.activeElement && panelElement.contains(document.activeElement);
const panelElement = document.getElementById(focusableUIElements.addonPanel);

fullAPI.togglePanel();

if (wasPanelShown && wasFocusInPanel) {
if (wasPanelShown && wasFocusInElement(panelElement)) {
// poll: true always returns a Promise.
(
fullAPI.focusOnUIElement(focusableUIElements.showAddonPanel, {
Expand All @@ -384,11 +386,10 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => {
case 'toggleNav': {
const wasNavShown = fullAPI.getIsNavShown();
const sidebarElement = document.getElementById(focusableUIElements.sidebarRegion);
const wasFocusInSidebar = sidebarElement?.contains(document?.activeElement);

fullAPI.toggleNav();

if (wasNavShown && wasFocusInSidebar) {
if (wasNavShown && wasFocusInElement(sidebarElement)) {
// poll: true always returns a Promise.
(
fullAPI.focusOnUIElement(focusableUIElements.showSidebar, {
Expand Down
4 changes: 2 additions & 2 deletions code/core/src/manager/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,15 @@ export const Layout = ({ managerLayoutState, setManagerLayoutState, hasTab, ...s
slotPages={slots.slotPages}
/>

{isDesktop && (
{isDesktop && showPanel && (
<PanelContainer
bottomPanelHeight={bottomPanelHeight}
rightPanelWidth={rightPanelWidth}
panelMaxSize={panelMaxSize}
panelResizerRef={panelResizerRef}
position={panelPosition}
>
{showPanel && slots.slotPanel}
{slots.slotPanel}
</PanelContainer>
)}
{isMobile && <Notifications />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,9 @@ export const ResetToDefaults: Story = {
const resetButton = await screen.findByRole('button', { name: 'Reset filters' });

expect(resetButton).toBeInTheDocument();
expect(resetButton).not.toBeDisabled();
expect(resetButton).not.toHaveAttribute('aria-disabled', 'true');
resetButton.click();
await waitFor(() => expect(resetButton).toBeDisabled());
await waitFor(() => expect(resetButton).toHaveAttribute('aria-disabled', 'true'));
},
} satisfies Story;

Expand Down
2 changes: 2 additions & 0 deletions code/core/src/manager/components/sidebar/TagsFilterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ interface TagsFilterPanelProps {
excludedFilters: string[];
}

/* Those tags are hidden in the UI. There's a more general built-in list defined in `shared/constants/tags`. */
const BUILT_IN_TAGS = new Set([
'dev',
'test',
Expand All @@ -67,6 +68,7 @@ const BUILT_IN_TAGS = new Set([
'unattached-mdx',
'play-fn',
'test-fn',
'manifest',
]);

// This equality check works on the basis that there are no duplicates in the arrays.
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/shared/checklist-store/checklistData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ export const Disabled: Story = {
await userEvent.click(button);

// 👇 Make assertions
await expect(button).toBeDisabled();
await expect(button).toHaveAttribute('aria-disabled', 'true');
await expect(args.onClick).not.toHaveBeenCalled();
}
};`}
Expand Down
2 changes: 1 addition & 1 deletion code/e2e-tests/addon-toolbars.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ test.describe('addon-toolbars', () => {
await expect(sbPage.previewRoot()).toContainText('안녕하세요');

const button = sbPage.page.getByLabel('Internationalization locale');
await expect(button).toBeDisabled();
await expect(button).toHaveAttribute('aria-disabled', 'true');
});
});
2 changes: 1 addition & 1 deletion code/e2e-tests/addon-viewport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ test.describe('addon-viewport', () => {

const toolbar = page.getByLabel('Viewport size');

await expect(toolbar).toBeDisabled();
await expect(toolbar).toHaveAttribute('aria-disabled', 'true');
});
});
49 changes: 49 additions & 0 deletions code/e2e-tests/framework-vue3.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect, test } from '@playwright/test';
import process from 'process';

import { SbPage } from './util';

const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:6006';
const templateName = process.env.STORYBOOK_TEMPLATE_NAME;

test.describe('Vue 3', () => {
test.beforeEach(async ({ page }) => {
await page.goto(storybookUrl);
await new SbPage(page, expect).waitUntilLoaded();
});

test.skip(templateName !== 'vue3-vite/default-ts', 'Only run these tests on Vue 3');

test('updateArgs works in decorators', async ({ page }) => {
const sbPage = new SbPage(page, expect);

await sbPage.navigateToStory(
'stories/renderers/vue3_vue3-vite-default-ts/decorators',
'update-args'
);
const previewRoot = sbPage.previewRoot();
const button = previewRoot.getByRole('button', { name: 'Add 1' });

await expect(previewRoot).toContainText('0');
await button.click();
await expect(previewRoot).toContainText('1');
await button.click();
await expect(previewRoot).toContainText('2');
});

test('Decorators can consume reactive globals', async ({ page }) => {
const sbPage = new SbPage(page, expect);

await sbPage.navigateToStory(
'stories/renderers/vue3_vue3-vite-default-ts/decorators',
'reactive-global-decorator'
);

// Check the original language
await expect(sbPage.previewRoot()).toContainText('Hello');

// Select spanish in the locale toolbar and check that the text changes
await sbPage.selectToolbar('[aria-label^="Internationalization locale"]', 'text=/Español/');
await expect(sbPage.previewRoot()).toContainText('Hola');
});
});
3 changes: 2 additions & 1 deletion code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,6 @@
"Dependency Upgrades"
]
]
}
},
"deferredNextVersion": "10.3.0-beta.2"
}
4 changes: 2 additions & 2 deletions code/renderers/react/template/stories/test-fn.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Default.test(
},
async ({ canvas }) => {
const button = canvas.getByText('Arg from story');
await expect(button).toBeEnabled();
await expect(button).not.toHaveAttribute('aria-disabled', 'true');
}
);
export const Extended = Default.extend({
Expand All @@ -88,5 +88,5 @@ export const Extended = Default.extend({
});
Extended.test('should have extended args', async ({ canvas }) => {
const button = canvas.getByText('Arg from extended story');
await expect(button).toBeEnabled();
await expect(button).not.toHaveAttribute('aria-disabled', 'true');
});
Loading
Loading