From 436fd97a30667eb593e873566f61ddc389a5641f Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 7 Mar 2026 15:30:23 +0100 Subject: [PATCH 01/10] Add Collapsible component to @wordpress/ui MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thin wrapper around Base UI's Collapsible component with Root, Trigger, and Panel subcomponents. No custom styles — this is a pure pass-through of the Base UI API. Made-with: Cursor --- packages/ui/src/collapsible/index.ts | 5 +++++ packages/ui/src/collapsible/panel.tsx | 16 ++++++++++++++++ packages/ui/src/collapsible/root.tsx | 15 +++++++++++++++ packages/ui/src/collapsible/trigger.tsx | 15 +++++++++++++++ packages/ui/src/collapsible/types.ts | 24 ++++++++++++++++++++++++ packages/ui/src/index.ts | 1 + 6 files changed, 76 insertions(+) create mode 100644 packages/ui/src/collapsible/index.ts create mode 100644 packages/ui/src/collapsible/panel.tsx create mode 100644 packages/ui/src/collapsible/root.tsx create mode 100644 packages/ui/src/collapsible/trigger.tsx create mode 100644 packages/ui/src/collapsible/types.ts diff --git a/packages/ui/src/collapsible/index.ts b/packages/ui/src/collapsible/index.ts new file mode 100644 index 00000000000000..8a2edfa94ec95d --- /dev/null +++ b/packages/ui/src/collapsible/index.ts @@ -0,0 +1,5 @@ +import { Panel } from './panel'; +import { Root } from './root'; +import { Trigger } from './trigger'; + +export { Root, Trigger, Panel }; diff --git a/packages/ui/src/collapsible/panel.tsx b/packages/ui/src/collapsible/panel.tsx new file mode 100644 index 00000000000000..1c8583fc0da793 --- /dev/null +++ b/packages/ui/src/collapsible/panel.tsx @@ -0,0 +1,16 @@ +import { forwardRef } from '@wordpress/element'; +import { Collapsible as _Collapsible } from '@base-ui/react/collapsible'; +import type { PanelProps } from './types'; + +/** + * A panel with the collapsible contents. Hidden when collapsed, visible + * when expanded. + * + * `Collapsible` is a collection of React components that combine to render + * a collapsible panel controlled by a button. + */ +export const Panel = forwardRef< HTMLDivElement, PanelProps >( + function CollapsiblePanel( { ...otherProps }, forwardedRef ) { + return <_Collapsible.Panel ref={ forwardedRef } { ...otherProps } />; + } +); diff --git a/packages/ui/src/collapsible/root.tsx b/packages/ui/src/collapsible/root.tsx new file mode 100644 index 00000000000000..02df2645a7723f --- /dev/null +++ b/packages/ui/src/collapsible/root.tsx @@ -0,0 +1,15 @@ +import { forwardRef } from '@wordpress/element'; +import { Collapsible as _Collapsible } from '@base-ui/react/collapsible'; +import type { RootProps } from './types'; + +/** + * Groups all parts of the collapsible. + * + * `Collapsible` is a collection of React components that combine to render + * a collapsible panel controlled by a button. + */ +export const Root = forwardRef< HTMLDivElement, RootProps >( + function CollapsibleRoot( { ...otherProps }, forwardedRef ) { + return <_Collapsible.Root ref={ forwardedRef } { ...otherProps } />; + } +); diff --git a/packages/ui/src/collapsible/trigger.tsx b/packages/ui/src/collapsible/trigger.tsx new file mode 100644 index 00000000000000..b0922c67673467 --- /dev/null +++ b/packages/ui/src/collapsible/trigger.tsx @@ -0,0 +1,15 @@ +import { forwardRef } from '@wordpress/element'; +import { Collapsible as _Collapsible } from '@base-ui/react/collapsible'; +import type { TriggerProps } from './types'; + +/** + * A button that opens and closes the collapsible panel. + * + * `Collapsible` is a collection of React components that combine to render + * a collapsible panel controlled by a button. + */ +export const Trigger = forwardRef< HTMLButtonElement, TriggerProps >( + function CollapsibleTrigger( { ...otherProps }, forwardedRef ) { + return <_Collapsible.Trigger ref={ forwardedRef } { ...otherProps } />; + } +); diff --git a/packages/ui/src/collapsible/types.ts b/packages/ui/src/collapsible/types.ts new file mode 100644 index 00000000000000..05950bf3b4bc18 --- /dev/null +++ b/packages/ui/src/collapsible/types.ts @@ -0,0 +1,24 @@ +import type { ReactNode } from 'react'; +import type { Collapsible as _Collapsible } from '@base-ui/react/collapsible'; +import type { ComponentProps } from '../utils/types'; + +export type RootProps = ComponentProps< typeof _Collapsible.Root > & { + /** + * The content to be rendered inside the component. + */ + children?: ReactNode; +}; + +export type TriggerProps = ComponentProps< typeof _Collapsible.Trigger > & { + /** + * The content to be rendered inside the component. + */ + children?: ReactNode; +}; + +export type PanelProps = ComponentProps< typeof _Collapsible.Panel > & { + /** + * The content to be rendered inside the component. + */ + children?: ReactNode; +}; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 75765c8a38212f..6f9a138744e15d 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,6 +1,7 @@ export * from './badge'; export * from './button'; export * as Card from './card'; +export * as Collapsible from './collapsible'; export * as CollapsibleCard from './collapsible-card'; export * as Dialog from './dialog'; export * from './form/primitives'; From fcce537dd8787828c10623bc5c9618e36e45b3c4 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 7 Mar 2026 15:31:05 +0100 Subject: [PATCH 02/10] Add Storybook stories for Collapsible Covers default, default-open, disabled, and controlled variants. Made-with: Cursor --- .../src/collapsible/stories/index.story.tsx | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 packages/ui/src/collapsible/stories/index.story.tsx diff --git a/packages/ui/src/collapsible/stories/index.story.tsx b/packages/ui/src/collapsible/stories/index.story.tsx new file mode 100644 index 00000000000000..99568e4458cc5b --- /dev/null +++ b/packages/ui/src/collapsible/stories/index.story.tsx @@ -0,0 +1,77 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { useState } from '@wordpress/element'; +import * as Collapsible from '../index'; + +const meta: Meta< typeof Collapsible.Root > = { + title: 'Design System/Components/Collapsible', + component: Collapsible.Root, + subcomponents: { + 'Collapsible.Trigger': Collapsible.Trigger, + 'Collapsible.Panel': Collapsible.Panel, + }, +}; +export default meta; + +type Story = StoryObj< typeof Collapsible.Root >; + +export const Default: Story = { + args: { + children: ( + <> + Toggle + +

Collapsible content here.

+
+ + ), + }, +}; + +export const DefaultOpen: Story = { + argTypes: { open: { control: false } }, + args: { + defaultOpen: true, + children: ( + <> + Toggle + +

This panel is open by default.

+
+ + ), + }, +}; + +export const Disabled: Story = { + args: { + disabled: true, + children: ( + <> + Toggle (disabled) + +

This content cannot be toggled.

+
+ + ), + }, +}; + +export const Controlled: Story = { + argTypes: { + open: { control: false }, + defaultOpen: { control: false }, + }, + render: function Controlled() { + const [ open, setOpen ] = useState( false ); + return ( + + + { open ? 'Close' : 'Open' } + + +

Controlled collapsible panel.

+
+
+ ); + }, +}; From 87ddb772a0dd44cf79ff6c2733b933c71e06c08d Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 7 Mar 2026 15:32:17 +0100 Subject: [PATCH 03/10] Add unit tests for Collapsible Covers ref forwarding, uncontrolled/controlled open state, disabled behavior, and className pass-through. Made-with: Cursor --- .../ui/src/collapsible/test/index.test.tsx | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 packages/ui/src/collapsible/test/index.test.tsx diff --git a/packages/ui/src/collapsible/test/index.test.tsx b/packages/ui/src/collapsible/test/index.test.tsx new file mode 100644 index 00000000000000..97a14e85059191 --- /dev/null +++ b/packages/ui/src/collapsible/test/index.test.tsx @@ -0,0 +1,186 @@ +import { render, screen } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; +import { createRef, useState } from '@wordpress/element'; +import * as Collapsible from '../index'; + +function UncontrolledCollapsible( { + defaultOpen, + disabled, +}: { + defaultOpen?: boolean; + disabled?: boolean; +} ) { + return ( + + Toggle + Panel content + + ); +} + +function ControlledCollapsible( { + onOpenChange, +}: { + onOpenChange?: ( open: boolean ) => void; +} ) { + const [ open, setOpen ] = useState( false ); + return ( + { + setOpen( nextOpen ); + onOpenChange?.( nextOpen ); + } } + > + Toggle + Controlled panel + + ); +} + +describe( 'Collapsible', () => { + describe( 'ref forwarding', () => { + it( 'forwards ref on Root', () => { + const ref = createRef< HTMLDivElement >(); + render( + + Toggle + Content + + ); + expect( ref.current ).toBeInstanceOf( HTMLDivElement ); + } ); + + it( 'forwards ref on Trigger', () => { + const ref = createRef< HTMLButtonElement >(); + render( + + + Toggle + + Content + + ); + expect( ref.current ).toBeInstanceOf( HTMLButtonElement ); + } ); + + it( 'forwards ref on Panel', () => { + const ref = createRef< HTMLDivElement >(); + render( + + Toggle + Content + + ); + expect( ref.current ).toBeInstanceOf( HTMLDivElement ); + } ); + } ); + + describe( 'uncontrolled', () => { + it( 'is collapsed by default', () => { + render( ); + expect( + screen.queryByText( 'Panel content' ) + ).not.toBeInTheDocument(); + } ); + + it( 'shows panel when defaultOpen is true', () => { + render( ); + expect( screen.getByText( 'Panel content' ) ).toBeVisible(); + } ); + + it( 'toggles panel on trigger click', async () => { + const user = userEvent.setup(); + render( ); + + expect( + screen.queryByText( 'Panel content' ) + ).not.toBeInTheDocument(); + + await user.click( + screen.getByRole( 'button', { name: 'Toggle' } ) + ); + expect( screen.getByText( 'Panel content' ) ).toBeVisible(); + + await user.click( + screen.getByRole( 'button', { name: 'Toggle' } ) + ); + expect( + screen.queryByText( 'Panel content' ) + ).not.toBeInTheDocument(); + } ); + } ); + + describe( 'controlled', () => { + it( 'calls onOpenChange when toggled', async () => { + const onOpenChange = jest.fn(); + const user = userEvent.setup(); + + render( ); + + await user.click( + screen.getByRole( 'button', { name: 'Toggle' } ) + ); + expect( onOpenChange ).toHaveBeenCalledWith( true ); + + await user.click( + screen.getByRole( 'button', { name: 'Toggle' } ) + ); + expect( onOpenChange ).toHaveBeenCalledWith( false ); + } ); + } ); + + describe( 'disabled', () => { + it( 'does not toggle when disabled', async () => { + const user = userEvent.setup(); + render( ); + + expect( screen.getByText( 'Panel content' ) ).toBeVisible(); + + await user.click( + screen.getByRole( 'button', { name: 'Toggle' } ) + ); + expect( screen.getByText( 'Panel content' ) ).toBeVisible(); + } ); + } ); + + describe( 'custom className', () => { + it( 'applies className to Root', () => { + const ref = createRef< HTMLDivElement >(); + render( + + Toggle + Content + + ); + expect( ref.current ).toHaveClass( 'custom-root' ); + } ); + + it( 'applies className to Trigger', () => { + render( + + + Toggle + + Content + + ); + expect( + screen.getByRole( 'button', { name: 'Toggle' } ) + ).toHaveClass( 'custom-trigger' ); + } ); + + it( 'applies className to Panel', () => { + const ref = createRef< HTMLDivElement >(); + render( + + Toggle + + Content + + + ); + expect( ref.current ).toHaveClass( 'custom-panel' ); + } ); + } ); +} ); From bddef1391f65264eb36f1419812fdf54f530300a Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 7 Mar 2026 15:32:51 +0100 Subject: [PATCH 04/10] Update CHANGELOG for Collapsible component Made-with: Cursor --- packages/ui/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 732fa6aa322674..1e5bea6e67afa8 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -7,6 +7,7 @@ - Add `Text` primitive with predefined typographic variants (`heading-2xl` through `heading-sm`, `body-xl` through `body-sm`) built on design tokens ([#75870](https://github.com/WordPress/gutenberg/pull/75870)). - Add `Card` and `CollapsibleCard` primitives ([#76252](https://github.com/WordPress/gutenberg/pull/76252)). - Add `Link` primitive ([#76013](https://github.com/WordPress/gutenberg/pull/76013)). +- Add `Collapsible` primitive ([#76280](https://github.com/WordPress/gutenberg/pull/76280)). ### Bug Fixes From a86353343446ca21679c9e4cf3fda15d839b2a46 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 7 Mar 2026 15:40:06 +0100 Subject: [PATCH 05/10] Address review feedback - Fix import ordering: external (@base-ui) before @wordpress - Add render prop tests for Root, Trigger, and Panel Made-with: Cursor --- packages/ui/src/collapsible/panel.tsx | 2 +- packages/ui/src/collapsible/root.tsx | 2 +- .../ui/src/collapsible/test/index.test.tsx | 42 +++++++++++++++++++ packages/ui/src/collapsible/trigger.tsx | 2 +- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/collapsible/panel.tsx b/packages/ui/src/collapsible/panel.tsx index 1c8583fc0da793..cf6c2fb7145949 100644 --- a/packages/ui/src/collapsible/panel.tsx +++ b/packages/ui/src/collapsible/panel.tsx @@ -1,5 +1,5 @@ -import { forwardRef } from '@wordpress/element'; import { Collapsible as _Collapsible } from '@base-ui/react/collapsible'; +import { forwardRef } from '@wordpress/element'; import type { PanelProps } from './types'; /** diff --git a/packages/ui/src/collapsible/root.tsx b/packages/ui/src/collapsible/root.tsx index 02df2645a7723f..adc9c848c525f4 100644 --- a/packages/ui/src/collapsible/root.tsx +++ b/packages/ui/src/collapsible/root.tsx @@ -1,5 +1,5 @@ -import { forwardRef } from '@wordpress/element'; import { Collapsible as _Collapsible } from '@base-ui/react/collapsible'; +import { forwardRef } from '@wordpress/element'; import type { RootProps } from './types'; /** diff --git a/packages/ui/src/collapsible/test/index.test.tsx b/packages/ui/src/collapsible/test/index.test.tsx index 97a14e85059191..ec4c94862f821e 100644 --- a/packages/ui/src/collapsible/test/index.test.tsx +++ b/packages/ui/src/collapsible/test/index.test.tsx @@ -144,6 +144,48 @@ describe( 'Collapsible', () => { } ); } ); + describe( 'render prop', () => { + it( 'supports render prop on Root', () => { + const ref = createRef< HTMLElement >(); + render( + }> + Toggle + Content + + ); + expect( ref.current?.tagName ).toBe( 'SECTION' ); + } ); + + it( 'supports render prop on Trigger', () => { + render( + + } + > + Toggle + + Content + + ); + const trigger = screen.getByRole( 'button', { name: 'Toggle' } ); + expect( trigger.tagName ).toBe( 'DIV' ); + } ); + + it( 'supports render prop on Panel', () => { + const ref = createRef< HTMLElement >(); + render( + + Toggle + }> + Content + + + ); + expect( ref.current?.tagName ).toBe( 'SECTION' ); + } ); + } ); + describe( 'custom className', () => { it( 'applies className to Root', () => { const ref = createRef< HTMLDivElement >(); diff --git a/packages/ui/src/collapsible/trigger.tsx b/packages/ui/src/collapsible/trigger.tsx index b0922c67673467..101ec47cb698e5 100644 --- a/packages/ui/src/collapsible/trigger.tsx +++ b/packages/ui/src/collapsible/trigger.tsx @@ -1,5 +1,5 @@ -import { forwardRef } from '@wordpress/element'; import { Collapsible as _Collapsible } from '@base-ui/react/collapsible'; +import { forwardRef } from '@wordpress/element'; import type { TriggerProps } from './types'; /** From d21d88cd2e1fad2bd0b2b21220448cd349e3f233 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 7 Mar 2026 15:53:07 +0100 Subject: [PATCH 06/10] Update CollapsibleCard to use internal Collapsible wrapper Replace direct @base-ui/react/collapsible imports with the new internal Collapsible component in all three CollapsibleCard files. In header.tsx, derive the open state from the aria-expanded prop instead of the Base UI state callback, since our wrapper's render prop type uses a single-argument signature. Made-with: Cursor --- packages/ui/src/collapsible-card/content.tsx | 2 +- packages/ui/src/collapsible-card/header.tsx | 11 ++++++++--- packages/ui/src/collapsible-card/root.tsx | 2 +- packages/ui/src/collapsible/test/index.test.tsx | 4 ++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/collapsible-card/content.tsx b/packages/ui/src/collapsible-card/content.tsx index ce4a740e2dc728..0f57970b68b2e9 100644 --- a/packages/ui/src/collapsible-card/content.tsx +++ b/packages/ui/src/collapsible-card/content.tsx @@ -1,6 +1,6 @@ -import { Collapsible } from '@base-ui/react/collapsible'; import { forwardRef } from '@wordpress/element'; import * as Card from '../card'; +import * as Collapsible from '../collapsible'; import type { ContentProps } from './types'; /** diff --git a/packages/ui/src/collapsible-card/header.tsx b/packages/ui/src/collapsible-card/header.tsx index a017bd29d8505f..5e0e4429b33b5b 100644 --- a/packages/ui/src/collapsible-card/header.tsx +++ b/packages/ui/src/collapsible-card/header.tsx @@ -1,10 +1,10 @@ -import { Collapsible } from '@base-ui/react/collapsible'; import clsx from 'clsx'; import type { MouseEvent } from 'react'; import { forwardRef, useCallback, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { chevronDown, chevronUp } from '@wordpress/icons'; import * as Card from '../card'; +import * as Collapsible from '../collapsible'; import { IconButton } from '../icon-button'; import styles from './style.module.css'; import type { HeaderProps } from './types'; @@ -52,11 +52,16 @@ export const Header = forwardRef< HTMLDivElement, HeaderProps >(
( + render={ ( props ) => ( { describe( 'render prop', () => { it( 'supports render prop on Root', () => { - const ref = createRef< HTMLElement >(); + const ref = createRef< HTMLDivElement >(); render( }> Toggle @@ -173,7 +173,7 @@ describe( 'Collapsible', () => { } ); it( 'supports render prop on Panel', () => { - const ref = createRef< HTMLElement >(); + const ref = createRef< HTMLDivElement >(); render( Toggle From 3fb13ed176d96c06983908e5a0b6d2c54155ec09 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sun, 8 Mar 2026 11:52:38 +0100 Subject: [PATCH 07/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/ui/src/collapsible-card/header.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ui/src/collapsible-card/header.tsx b/packages/ui/src/collapsible-card/header.tsx index 5e0e4429b33b5b..52f93cc22ab0f1 100644 --- a/packages/ui/src/collapsible-card/header.tsx +++ b/packages/ui/src/collapsible-card/header.tsx @@ -57,8 +57,7 @@ export const Header = forwardRef< HTMLDivElement, HeaderProps >( { ...props } label={ __( 'Expand or collapse card' ) } icon={ - String( props[ 'aria-expanded' ] ) === - 'true' + props[ 'aria-expanded' ] === true ? chevronUp : chevronDown } From 85cd618af537c594b76c00fa6abf57fbe5c35cfb Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sun, 8 Mar 2026 12:35:33 +0100 Subject: [PATCH 08/10] Add comment explaining aria-expanded tradeoff in header render prop The Collapsible wrapper's ComponentProps utility narrows the render callback to a single-argument signature, so Base UI's `state` object isn't available. Document why we read open state from `aria-expanded`. Made-with: Cursor --- packages/ui/src/collapsible-card/header.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/ui/src/collapsible-card/header.tsx b/packages/ui/src/collapsible-card/header.tsx index 52f93cc22ab0f1..cf4c880bb5aabe 100644 --- a/packages/ui/src/collapsible-card/header.tsx +++ b/packages/ui/src/collapsible-card/header.tsx @@ -56,6 +56,12 @@ export const Header = forwardRef< HTMLDivElement, HeaderProps >( Date: Fri, 13 Mar 2026 17:59:48 +0100 Subject: [PATCH 09/10] avoid unnecessary destructuring --- packages/ui/src/collapsible/panel.tsx | 4 ++-- packages/ui/src/collapsible/root.tsx | 4 ++-- packages/ui/src/collapsible/trigger.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/collapsible/panel.tsx b/packages/ui/src/collapsible/panel.tsx index cf6c2fb7145949..7348073857d0d5 100644 --- a/packages/ui/src/collapsible/panel.tsx +++ b/packages/ui/src/collapsible/panel.tsx @@ -10,7 +10,7 @@ import type { PanelProps } from './types'; * a collapsible panel controlled by a button. */ export const Panel = forwardRef< HTMLDivElement, PanelProps >( - function CollapsiblePanel( { ...otherProps }, forwardedRef ) { - return <_Collapsible.Panel ref={ forwardedRef } { ...otherProps } />; + function CollapsiblePanel( props, forwardedRef ) { + return <_Collapsible.Panel ref={ forwardedRef } { ...props } />; } ); diff --git a/packages/ui/src/collapsible/root.tsx b/packages/ui/src/collapsible/root.tsx index adc9c848c525f4..6be50799f69a6e 100644 --- a/packages/ui/src/collapsible/root.tsx +++ b/packages/ui/src/collapsible/root.tsx @@ -9,7 +9,7 @@ import type { RootProps } from './types'; * a collapsible panel controlled by a button. */ export const Root = forwardRef< HTMLDivElement, RootProps >( - function CollapsibleRoot( { ...otherProps }, forwardedRef ) { - return <_Collapsible.Root ref={ forwardedRef } { ...otherProps } />; + function CollapsibleRoot( props, forwardedRef ) { + return <_Collapsible.Root ref={ forwardedRef } { ...props } />; } ); diff --git a/packages/ui/src/collapsible/trigger.tsx b/packages/ui/src/collapsible/trigger.tsx index 101ec47cb698e5..a789970b3ca51f 100644 --- a/packages/ui/src/collapsible/trigger.tsx +++ b/packages/ui/src/collapsible/trigger.tsx @@ -9,7 +9,7 @@ import type { TriggerProps } from './types'; * a collapsible panel controlled by a button. */ export const Trigger = forwardRef< HTMLButtonElement, TriggerProps >( - function CollapsibleTrigger( { ...otherProps }, forwardedRef ) { - return <_Collapsible.Trigger ref={ forwardedRef } { ...otherProps } />; + function CollapsibleTrigger( props, forwardedRef ) { + return <_Collapsible.Trigger ref={ forwardedRef } { ...props } />; } ); From b9d7fcc3f3cbdc836d6dd6037f66ef808b489b41 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 13 Mar 2026 17:59:59 +0100 Subject: [PATCH 10/10] add hidden until found example --- .../src/collapsible/stories/index.story.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/ui/src/collapsible/stories/index.story.tsx b/packages/ui/src/collapsible/stories/index.story.tsx index 99568e4458cc5b..10960afe6458e3 100644 --- a/packages/ui/src/collapsible/stories/index.story.tsx +++ b/packages/ui/src/collapsible/stories/index.story.tsx @@ -56,6 +56,37 @@ export const Disabled: Story = { }, }; +/** + * When `hiddenUntilFound` is set on `Collapsible.Panel`, the collapsed content + * remains in the DOM using the `hidden="until-found"` HTML attribute instead of + * being removed entirely. This lets the browser's native page search (Ctrl/Cmd+F) + * find text inside collapsed panels and automatically expand them to reveal the + * match — improving discoverability without sacrificing the collapsed layout. + */ +export const HiddenUntilFound: Story = { + render: function HiddenUntilFound() { + return ( +
+

+ Use the browser's find-in-page (Ctrl/Cmd+F) to search + for "hidden treasure". The collapsed panel will + automatically expand to reveal the match. +

+ + Expand to reveal + +

+ This is the hidden treasure that can be found via + the browser's built-in page search even while + the panel is collapsed. +

+
+
+
+ ); + }, +}; + export const Controlled: Story = { argTypes: { open: { control: false },