From 7121f749ef26fbf0543654affaf2dc5c03dbf749 Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Thu, 29 Jan 2026 15:37:46 +0500 Subject: [PATCH 1/8] Fix Edit JSON button accessibility on small screens (WCAG 2.1 Reflow) --- .../addons/docs/src/blocks/controls/Object.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Object.tsx b/code/addons/docs/src/blocks/controls/Object.tsx index a817cd1e368a..a6a64ef06e39 100644 --- a/code/addons/docs/src/blocks/controls/Object.tsx +++ b/code/addons/docs/src/blocks/controls/Object.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Button, Form, ToggleButton } from 'storybook/internal/components'; -import { AddIcon, SubtractIcon } from '@storybook/icons'; +import { AddIcon, EditIcon, SubtractIcon } from '@storybook/icons'; import { cloneDeep } from 'es-toolkit/object'; import { type Theme, styled, useTheme } from 'storybook/theming'; @@ -19,6 +19,7 @@ type JsonTreeProps = ComponentProps; const Wrapper = styled.div(({ theme }) => ({ position: 'relative', display: 'flex', + flexWrap: 'wrap', isolation: 'isolate', '.rejt-tree': { @@ -118,10 +119,14 @@ const Input = styled.input(({ theme, placeholder }) => ({ })); const RawButton = styled(ToggleButton)({ - position: 'absolute', - zIndex: 2, - top: 2, - right: 2, + flexShrink: 0, + + // Hide text on small screens, show only icon for accessibility (WCAG 2.1 Reflow) + '@media (max-width: 400px)': { + '& > span': { + display: 'none', + }, + }, }); const RawInput = styled(Form.Textarea)(({ theme }) => ({ @@ -256,7 +261,8 @@ export const ObjectControl: FC = ({ name, value, onChange, argType setShowRaw((isRaw) => !isRaw); }} > - Edit JSON + + Edit JSON )} {!showRaw ? ( From e5efb51799499275e05b94faf2c2da1fac614609 Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Thu, 29 Jan 2026 15:50:32 +0500 Subject: [PATCH 2/8] Add gap between icon and text in Edit JSON button --- code/addons/docs/src/blocks/controls/Object.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/code/addons/docs/src/blocks/controls/Object.tsx b/code/addons/docs/src/blocks/controls/Object.tsx index a6a64ef06e39..03218c27b5dc 100644 --- a/code/addons/docs/src/blocks/controls/Object.tsx +++ b/code/addons/docs/src/blocks/controls/Object.tsx @@ -120,6 +120,7 @@ const Input = styled.input(({ theme, placeholder }) => ({ const RawButton = styled(ToggleButton)({ flexShrink: 0, + gap: '4px', // Hide text on small screens, show only icon for accessibility (WCAG 2.1 Reflow) '@media (max-width: 400px)': { From 2c3c4bddd8eb7d8fb7359baa7df950ec0bed4509 Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Tue, 10 Feb 2026 15:05:04 +0500 Subject: [PATCH 3/8] fix: restore absolute positioning for Edit JSON button on large screens, use column layout on small screens for WCAG 2.1 Reflow --- code/addons/docs/src/blocks/controls/Object.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Object.tsx b/code/addons/docs/src/blocks/controls/Object.tsx index 03218c27b5dc..8e9d0b3a5c46 100644 --- a/code/addons/docs/src/blocks/controls/Object.tsx +++ b/code/addons/docs/src/blocks/controls/Object.tsx @@ -19,9 +19,12 @@ type JsonTreeProps = ComponentProps; const Wrapper = styled.div(({ theme }) => ({ position: 'relative', display: 'flex', - flexWrap: 'wrap', isolation: 'isolate', + '@media (max-width: 400px)': { + flexDirection: 'column' as const, + }, + '.rejt-tree': { marginLeft: '1rem', fontSize: '13px', @@ -119,11 +122,16 @@ const Input = styled.input(({ theme, placeholder }) => ({ })); const RawButton = styled(ToggleButton)({ - flexShrink: 0, + position: 'absolute', + zIndex: 2, + top: 2, + right: 2, gap: '4px', - // Hide text on small screens, show only icon for accessibility (WCAG 2.1 Reflow) + // On small screens: remove absolute positioning, show only icon (WCAG 2.1 Reflow) '@media (max-width: 400px)': { + position: 'static', + alignSelf: 'flex-end', '& > span': { display: 'none', }, From 310a764cb72752fa51170f7c45aae0deee1c3ebc Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Tue, 10 Feb 2026 17:36:19 +0500 Subject: [PATCH 4/8] Add small viewport stories for Chromatic coverage --- .../src/blocks/controls/Object.stories.tsx | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/code/addons/docs/src/blocks/controls/Object.stories.tsx b/code/addons/docs/src/blocks/controls/Object.stories.tsx index de883570198b..50146d080f31 100644 --- a/code/addons/docs/src/blocks/controls/Object.stories.tsx +++ b/code/addons/docs/src/blocks/controls/Object.stories.tsx @@ -112,3 +112,31 @@ export const ReadonlyAndUndefined: Story = { argType: { table: { readonly: true } }, }, }; + +export const ObjectSmallViewport: Story = { + args: { + value: { + name: 'Michael', + someDate: new Date('2022-10-30T12:31:11'), + nested: { someBool: true, someNumber: 22 }, + }, + }, + parameters: { + chromatic: { viewports: [320] }, + }, +}; + +export const ArraySmallViewport: Story = { + args: { + value: [ + 'someString', + 22, + true, + new Date('2022-10-30T12:31:11'), + { someBool: true, someNumber: 22 }, + ], + }, + parameters: { + chromatic: { viewports: [320] }, + }, +}; From f45870f3c8302fde16bf07b5b5bd7f84e594a82e Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Fri, 20 Feb 2026 11:01:31 +0500 Subject: [PATCH 5/8] refactor: use sr-only styles instead of display:none and add container queries --- .../docs/src/blocks/controls/Object.tsx | 20 ++++++++++---- code/core/src/manager/globals/exports.ts | 1 + code/core/src/theming/global.ts | 26 ++++++++++--------- code/core/src/theming/index.ts | 2 +- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Object.tsx b/code/addons/docs/src/blocks/controls/Object.tsx index 8e9d0b3a5c46..bf67b74a0b3b 100644 --- a/code/addons/docs/src/blocks/controls/Object.tsx +++ b/code/addons/docs/src/blocks/controls/Object.tsx @@ -6,7 +6,7 @@ import { Button, Form, ToggleButton } from 'storybook/internal/components'; import { AddIcon, EditIcon, SubtractIcon } from '@storybook/icons'; import { cloneDeep } from 'es-toolkit/object'; -import { type Theme, styled, useTheme } from 'storybook/theming'; +import { type Theme, styled, useTheme, srOnlyStyles } from 'storybook/theming'; import { getControlId, getControlSetterButtonId } from './helpers'; import { JsonTree } from './react-editable-json-tree'; @@ -21,6 +21,11 @@ const Wrapper = styled.div(({ theme }) => ({ display: 'flex', isolation: 'isolate', + // Enable container queries for child responsive styles + '@supports (container-type: inline-size)': { + containerType: 'inline-size', + }, + '@media (max-width: 400px)': { flexDirection: 'column' as const, }, @@ -128,13 +133,18 @@ const RawButton = styled(ToggleButton)({ right: 2, gap: '4px', - // On small screens: remove absolute positioning, show only icon (WCAG 2.1 Reflow) + // Container query: respond to component width (WCAG 2.1 Reflow) + '@container (max-width: 400px)': { + position: 'static', + alignSelf: 'flex-end', + '& > span': srOnlyStyles, + }, + + // Fallback for browsers without container query support '@media (max-width: 400px)': { position: 'static', alignSelf: 'flex-end', - '& > span': { - display: 'none', - }, + '& > span': srOnlyStyles, }, }); diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index 59703619f1de..1cecea844ec2 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -380,6 +380,7 @@ export default { 'jsx', 'keyframes', 'lighten', + 'srOnlyStyles', 'styled', 'themes', 'tokens', diff --git a/code/core/src/theming/global.ts b/code/core/src/theming/global.ts index ba0f089b1603..3b3d69f9d54c 100644 --- a/code/core/src/theming/global.ts +++ b/code/core/src/theming/global.ts @@ -9,6 +9,19 @@ interface Return { }; } +export const srOnlyStyles = { + position: 'absolute' as const, + width: 1, + height: 1, + padding: 0, + margin: -1, + overflow: 'hidden', + whiteSpace: 'nowrap' as const, + clip: 'rect(0, 0, 0, 0)', + clipPath: 'inset(50%)', + border: 0, +}; + export const createReset = memoize(1)( ({ typography }: { typography: Typography }): Return => ({ body: { @@ -112,18 +125,7 @@ export const createGlobal = memoize(1)(({ borderTop: `1px solid ${color.border}`, }, - '.sb-sr-only, .sb-hidden-until-focus:not(:focus)': { - position: 'absolute', - width: 1, - height: 1, - padding: 0, - margin: -1, - overflow: 'hidden', - whiteSpace: 'nowrap', - clip: 'rect(0, 0, 0, 0)', - clipPath: 'inset(50%)', - border: 0, - }, + '.sb-sr-only, .sb-hidden-until-focus:not(:focus)': srOnlyStyles, '.sb-hidden-until-focus': { opacity: 0, diff --git a/code/core/src/theming/index.ts b/code/core/src/theming/index.ts index 7650c87b0b39..585e4fd5ff35 100644 --- a/code/core/src/theming/index.ts +++ b/code/core/src/theming/index.ts @@ -33,7 +33,7 @@ export * from './types'; export { default as createCache } from '@emotion/cache'; export { default as isPropValid } from '@emotion/is-prop-valid'; -export { createGlobal, createReset } from './global'; +export { createGlobal, createReset, srOnlyStyles } from './global'; export * from './create'; export * from './convert'; export * from './ensure'; From 46702456ae38aeb969a7f7805f725c6941b10343 Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Wed, 25 Feb 2026 09:29:18 +0500 Subject: [PATCH 6/8] fix: sort imports alphabetically in Object.tsx --- code/addons/docs/src/blocks/controls/Object.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/addons/docs/src/blocks/controls/Object.tsx b/code/addons/docs/src/blocks/controls/Object.tsx index bf67b74a0b3b..2718c545f091 100644 --- a/code/addons/docs/src/blocks/controls/Object.tsx +++ b/code/addons/docs/src/blocks/controls/Object.tsx @@ -6,7 +6,7 @@ import { Button, Form, ToggleButton } from 'storybook/internal/components'; import { AddIcon, EditIcon, SubtractIcon } from '@storybook/icons'; import { cloneDeep } from 'es-toolkit/object'; -import { type Theme, styled, useTheme, srOnlyStyles } from 'storybook/theming'; +import { type Theme, srOnlyStyles, styled, useTheme } from 'storybook/theming'; import { getControlId, getControlSetterButtonId } from './helpers'; import { JsonTree } from './react-editable-json-tree'; From 63c7cf947d039c5542f0003226d50d8f0b5b901a Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Wed, 25 Feb 2026 10:19:38 +0100 Subject: [PATCH 7/8] Object: Fix up layout issues --- code/addons/docs/src/blocks/controls/Object.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Object.tsx b/code/addons/docs/src/blocks/controls/Object.tsx index 2718c545f091..4b6ddbb2fddf 100644 --- a/code/addons/docs/src/blocks/controls/Object.tsx +++ b/code/addons/docs/src/blocks/controls/Object.tsx @@ -20,17 +20,15 @@ const Wrapper = styled.div(({ theme }) => ({ position: 'relative', display: 'flex', isolation: 'isolate', + gap: 8, // Enable container queries for child responsive styles '@supports (container-type: inline-size)': { containerType: 'inline-size', }, - '@media (max-width: 400px)': { - flexDirection: 'column' as const, - }, - '.rejt-tree': { + flex: 1, marginLeft: '1rem', fontSize: '13px', listStyleType: 'none', @@ -136,14 +134,16 @@ const RawButton = styled(ToggleButton)({ // Container query: respond to component width (WCAG 2.1 Reflow) '@container (max-width: 400px)': { position: 'static', - alignSelf: 'flex-end', + alignSelf: 'flex-start', + order: 2, '& > span': srOnlyStyles, }, // Fallback for browsers without container query support '@media (max-width: 400px)': { position: 'static', - alignSelf: 'flex-end', + alignSelf: 'flex-start', + order: 2, '& > span': srOnlyStyles, }, }); From 5ffc0e1703a624150ddaff7725e0c8bb901cfb8c Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Fri, 27 Feb 2026 16:30:58 +0100 Subject: [PATCH 8/8] UI: Rework edit button with instructions from MA --- .../docs/src/blocks/controls/Object.tsx | 39 +++++-------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Object.tsx b/code/addons/docs/src/blocks/controls/Object.tsx index 4b6ddbb2fddf..de2c6d81ace4 100644 --- a/code/addons/docs/src/blocks/controls/Object.tsx +++ b/code/addons/docs/src/blocks/controls/Object.tsx @@ -6,7 +6,7 @@ import { Button, Form, ToggleButton } from 'storybook/internal/components'; import { AddIcon, EditIcon, SubtractIcon } from '@storybook/icons'; import { cloneDeep } from 'es-toolkit/object'; -import { type Theme, srOnlyStyles, styled, useTheme } from 'storybook/theming'; +import { type Theme, styled, useTheme } from 'storybook/theming'; import { getControlId, getControlSetterButtonId } from './helpers'; import { JsonTree } from './react-editable-json-tree'; @@ -22,11 +22,6 @@ const Wrapper = styled.div(({ theme }) => ({ isolation: 'isolate', gap: 8, - // Enable container queries for child responsive styles - '@supports (container-type: inline-size)': { - containerType: 'inline-size', - }, - '.rejt-tree': { flex: 1, marginLeft: '1rem', @@ -125,27 +120,9 @@ const Input = styled.input(({ theme, placeholder }) => ({ })); const RawButton = styled(ToggleButton)({ - position: 'absolute', - zIndex: 2, - top: 2, - right: 2, - gap: '4px', - - // Container query: respond to component width (WCAG 2.1 Reflow) - '@container (max-width: 400px)': { - position: 'static', - alignSelf: 'flex-start', - order: 2, - '& > span': srOnlyStyles, - }, - - // Fallback for browsers without container query support - '@media (max-width: 400px)': { - position: 'static', - alignSelf: 'flex-start', - order: 2, - '& > span': srOnlyStyles, - }, + alignSelf: 'flex-start', + order: 2, + marginRight: -10, }); const RawInput = styled(Form.Textarea)(({ theme }) => ({ @@ -222,7 +199,7 @@ export const ObjectControl: FC = ({ name, value, onChange, argType const onForceVisible = useCallback(() => { onChange({}); setForceVisible(true); - }, [setForceVisible]); + }, [onChange, setForceVisible]); const htmlElRef = useRef(null); useEffect(() => { @@ -274,14 +251,16 @@ export const ObjectControl: FC = ({ name, value, onChange, argType { e.preventDefault(); setShowRaw((isRaw) => !isRaw); }} + variant="ghost" + padding="small" + size="small" > - Edit JSON )} {!showRaw ? (