Skip to content

Docs: Ensure unique control id attributes across multiple Controls blocks#34021

Merged
valentinpalkovic merged 3 commits into
storybookjs:nextfrom
TheSeydiCharyyev:fix/issue-26144-duplicated-control-ids
Mar 19, 2026
Merged

Docs: Ensure unique control id attributes across multiple Controls blocks#34021
valentinpalkovic merged 3 commits into
storybookjs:nextfrom
TheSeydiCharyyev:fix/issue-26144-duplicated-control-ids

Conversation

@TheSeydiCharyyev
Copy link
Copy Markdown
Contributor

@TheSeydiCharyyev TheSeydiCharyyev commented Mar 5, 2026

Closes #26144

What I did

When multiple <Controls> blocks render on the same docs page (for different stories), all controls generate identical id attributes (e.g., control-disabled). This causes <label htmlFor> to always target the control in the first table instead of the correct one.

Root cause: getControlId(name) in helpers.ts only uses the arg name, without any story context.

Fix: Added an optional storyId parameter to getControlId() and getControlSetterButtonId(), and threaded it through the component chain: Controls → ArgsTable → ArgRow → ArgControl → all control components. When storyId is provided, IDs become unique per story (e.g., control-story--name-disabled). Existing usage without storyId is unaffected.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

  1. Run a sandbox: yarn task --task sandbox --start-from auto --template react-vite/default-ts
  2. Open Storybook in your browser
  3. Navigate to a docs page that has multiple <Controls> blocks for different stories (e.g., create an MDX page with two <Controls of={Story1} /> and <Controls of={Story2} />)
  4. Inspect the control elements — each control id should now include the story ID prefix, making them unique across tables
  5. Click on a label in the second table — it should toggle/focus the control in that table, not in the first one

The MultipleControlsOnSamePage story in Controls.stories.tsx also verifies this automatically — it renders two Controls blocks and asserts all id attributes are unique.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be
    found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c9e91069-b0a4-4d6e-9121-67dbf7ef357a

📥 Commits

Reviewing files that changed from the base of the PR and between 5c779ce and 3edb538.

📒 Files selected for processing (1)
  • code/addons/docs/src/blocks/blocks/Controls.stories.tsx

📝 Walkthrough

Walkthrough

Threads a new optional storyId prop from Controls.tsx through ArgsTable → ArgRow → ArgControl into individual control components and helper functions; storyId is used to generate story-scoped HTML element IDs.

Changes

Cohort / File(s) Summary
Entry / Stories
code/addons/docs/src/blocks/blocks/Controls.tsx, code/addons/docs/src/blocks/blocks/Controls.stories.tsx
Passes storyId={story.id} to PureArgsTable/TabbedArgsTable; adds a story that renders multiple Controls blocks and asserts control IDs are unique.
Args table layer
code/addons/docs/src/blocks/components/ArgsTable/ArgsTable.tsx, code/addons/docs/src/blocks/components/ArgsTable/ArgRow.tsx, code/addons/docs/src/blocks/components/ArgsTable/ArgControl.tsx
Added optional storyId to props/interfaces and threaded it through destructuring and the common props passed to rows/controls.
Control prop types & helpers
code/addons/docs/src/blocks/controls/types.ts, code/addons/docs/src/blocks/controls/helpers.ts, code/addons/docs/src/blocks/controls/helpers.test.ts
Added storyId?: string to ControlProps; updated getControlId and getControlSetterButtonId signatures to accept optional storyId and include it in generated IDs; added tests for the behavior.
Control implementations
code/addons/docs/src/blocks/controls/*.tsx, code/addons/docs/src/blocks/controls/options/*
code/addons/docs/src/blocks/controls/Boolean.tsx, Color.tsx, Date.tsx, Files.tsx, Number.tsx, Object.tsx, Range.tsx, Text.tsx, options/Checkbox.tsx, options/Radio.tsx, options/Select.tsx
Control components now accept/destructure storyId and pass it to getControlId and, where applicable, getControlSetterButtonId, causing element IDs to be story-scoped.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(220,240,255,0.5)
    participant Page
    end
    participant Controls
    participant ArgsTable
    participant ArgRow
    participant ArgControl
    participant ControlComponent
    participant Helpers

    Page->>Controls: render (story.id)
    Controls->>ArgsTable: render with storyId
    ArgsTable->>ArgRow: render row (includes storyId)
    ArgRow->>ArgControl: render control (includes storyId)
    ArgControl->>ControlComponent: instantiate control props (includes storyId)
    ControlComponent->>Helpers: getControlId(name, storyId)
    Helpers-->>ControlComponent: return scoped id
    ControlComponent-->>Page: render element with scoped id
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/addons/docs/src/blocks/controls/types.ts`:
- Line 5: ControlProps currently only uses storyId for scoping which allows
collisions for multiple Controls blocks bound to the same story; add an optional
per-block token (e.g., controlsId?: string) to the ControlProps interface in
types.ts and update any ID generation helpers (the functions that build control
element IDs and the code referencing storyId) to include this controlsId when
present so each <Controls of={...}> block can produce unique IDs; ensure
defaults preserve existing behavior when controlsId is undefined and propagate
the new property through components/functions that call the ID helpers (search
for ControlProps, storyId, and the control ID helper functions to update).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4926d203-a8a7-450b-a9ea-77e7e33cebb8

📥 Commits

Reviewing files that changed from the base of the PR and between 670866d and 5c779ce.

📒 Files selected for processing (18)
  • code/addons/docs/src/blocks/blocks/Controls.tsx
  • code/addons/docs/src/blocks/components/ArgsTable/ArgControl.tsx
  • code/addons/docs/src/blocks/components/ArgsTable/ArgRow.tsx
  • code/addons/docs/src/blocks/components/ArgsTable/ArgsTable.tsx
  • code/addons/docs/src/blocks/controls/Boolean.tsx
  • code/addons/docs/src/blocks/controls/Color.tsx
  • code/addons/docs/src/blocks/controls/Date.tsx
  • code/addons/docs/src/blocks/controls/Files.tsx
  • code/addons/docs/src/blocks/controls/Number.tsx
  • code/addons/docs/src/blocks/controls/Object.tsx
  • code/addons/docs/src/blocks/controls/Range.tsx
  • code/addons/docs/src/blocks/controls/Text.tsx
  • code/addons/docs/src/blocks/controls/helpers.test.ts
  • code/addons/docs/src/blocks/controls/helpers.ts
  • code/addons/docs/src/blocks/controls/options/Checkbox.tsx
  • code/addons/docs/src/blocks/controls/options/Radio.tsx
  • code/addons/docs/src/blocks/controls/options/Select.tsx
  • code/addons/docs/src/blocks/controls/types.ts


export interface ControlProps<T> {
name: string;
storyId?: string;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

storyId alone can still collide for repeated <Controls of={sameStory}> blocks.
On Line 5, using only storyId as scope means two Controls blocks bound to the same story will still generate identical control IDs. Consider extending ControlProps with an optional per-block scope token (e.g., controlsId) and threading that into ID helpers for full uniqueness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/addons/docs/src/blocks/controls/types.ts` at line 5, ControlProps
currently only uses storyId for scoping which allows collisions for multiple
Controls blocks bound to the same story; add an optional per-block token (e.g.,
controlsId?: string) to the ControlProps interface in types.ts and update any ID
generation helpers (the functions that build control element IDs and the code
referencing storyId) to include this controlsId when present so each <Controls
of={...}> block can produce unique IDs; ensure defaults preserve existing
behavior when controlsId is undefined and propagate the new property through
components/functions that call the ID helpers (search for ControlProps, storyId,
and the control ID helper functions to update).

Copy link
Copy Markdown
Contributor

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @TheSeydiCharyyev!

Before going into the code review, could you please

  • Edit the PR description to match our PR template, with the "Manual testing" section written in?
  • Add a story that specifically tests the bug you're addressing with multiple ArgsTables for different stories in the same docs page?

Thanks,

@TheSeydiCharyyev
Copy link
Copy Markdown
Contributor Author

@Sidnioulz Thanks for the review! I've updated the PR description to match the template with the Manual testing section.

Regarding the story — I already have MultipleControlsOnSamePage in Controls.stories.tsx that renders two blocks for different stories and asserts all control id attributes are unique. Let me know if you'd like a different kind of test!

@TheSeydiCharyyev
Copy link
Copy Markdown
Contributor Author

@Sidnioulz Hey! Just following up — I've updated the PR description with the Manual testing section and added the MultipleControlsOnSamePage story as requested. Let me know if anything else is needed!

@Sidnioulz Sidnioulz self-requested a review March 11, 2026 11:31
@Sidnioulz Sidnioulz self-assigned this Mar 11, 2026
@Sidnioulz Sidnioulz dismissed their stale review March 11, 2026 11:32

I am Sidnioulz. Gotta re-review

@Sidnioulz
Copy link
Copy Markdown
Contributor

@Sidnioulz Hey! Just following up — I've updated the PR description with the Manual testing section and added the MultipleControlsOnSamePage story as requested. Let me know if anything else is needed!

Thanks! I have a bit of backlog but I'll look at it in the coming days :)

@Sidnioulz Sidnioulz changed the title fix: ensure unique control id attributes across multiple Controls blocks on docs page Docs: Ensure unique control id attributes across multiple Controls blocks Mar 11, 2026
Copy link
Copy Markdown
Contributor

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @TheSeydiCharyyev! The angular CI failure is likely due to an external factor. I'll check it out and will let you know if you have anything left to address!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Duplicated control id attributes when using multiple <Controls> elements on the same docs page

3 participants