Skip to content

fix(Breadcrumb)!: BREAKING CHANGE use composition and remove cloneElement#695

Merged
paanSinghCoder merged 5 commits intomainfrom
fix/breadcrumb-composition
Mar 23, 2026
Merged

fix(Breadcrumb)!: BREAKING CHANGE use composition and remove cloneElement#695
paanSinghCoder merged 5 commits intomainfrom
fix/breadcrumb-composition

Conversation

@paanSinghCoder
Copy link
Contributor

@paanSinghCoder paanSinghCoder commented Mar 12, 2026

BREAKING CHANGE

Migration

  • Replace as={<NextLink href="/" />} with as={NextLink} and move href (and other link props) to Breadcrumb.Item, e.g. <Breadcrumb.Item href="/" as={NextLink}>Home</Breadcrumb.Item>.

Summary

  • Replaces cloneElement with a composition-style API for the as prop and ensures ref and props are passed correctly.
  • Renders the current page item as a non-link for semantics and accessibility.

Changes

  • as prop (breaking)
    • Before: as={<NextLink href="/" />} (React element).
    • After:as={NextLink}(component reference). Put href and other props on Breadcrumb.Item; they are passed through to the component.
    • Implementation uses <Component ref={ref} className={...} href={href} {...props}>{label}</Component> (no cloneElement). Ref is forwarded to the link or custom component (except in the dropdown path).

Current page

  • When current is true, the item renders as <span aria-current="page"> instead of a link so the current page is not a clickable link.

Link content

  • When there is no leading icon, link content is no longer wrapped in an extra , so the DOM is <a>Home</a> for simple items. With a leading icon, the existing span structure is kept for layout.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactor (no functional changes, no bug fixes just code improvements)
  • Chore (changes to the build process or auxiliary tools and libraries such as documentation generation)
  • Style (changes that do not affect the meaning of the code (white-space, formatting, etc))
  • Test (adding missing tests or correcting existing tests)
  • Improvement (Improvements to existing code)
  • Other (please specify)

How Has This Been Tested?

[Describe the tests that you ran to verify your changes]

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (.mdx files)
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Screenshots (if appropriate):

[Add screenshots here]

Related Issues

[Link any related issues here using #issue-number]

Summary by CodeRabbit

  • New Features

    • Added ref forwarding support for breadcrumb items using the render prop
    • Added optional aria-label prop to breadcrumb root element
  • Bug Fixes

    • Fixed active breadcrumb item accessibility by rendering as <span> with aria-current="page" attribute
  • Documentation

    • Updated breadcrumb component documentation and examples to reflect the render prop API

@vercel
Copy link

vercel bot commented Mar 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Mar 23, 2026 6:13am

@paanSinghCoder paanSinghCoder self-assigned this Mar 12, 2026
@paanSinghCoder paanSinghCoder removed the request for review from ravisuhag March 12, 2026 04:12
@paanSinghCoder paanSinghCoder changed the title fix: use composition and remove cloneElement fix(Breadcrumb): BREAKING CHANGE use composition and remove cloneElement Mar 12, 2026
@paanSinghCoder paanSinghCoder changed the title fix(Breadcrumb): BREAKING CHANGE use composition and remove cloneElement fix(Breadcrumb)!: BREAKING CHANGE use composition and remove cloneElement Mar 12, 2026
Copy link
Contributor

@rohanchkrabrty rohanchkrabrty left a comment

Choose a reason for hiding this comment

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

Let's support render prop using useRender from Base UI, instead of as.
Docs - https://base-ui.com/react/utils/use-render

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

The pull request migrates the breadcrumb component's polymorphic customization API from an as prop to a render prop, leveraging the useRender hook for consistent prop handling and ref forwarding. BreadcrumbRoot gains ref forwarding and optional aria-label support. Documentation, tests, and examples are updated accordingly.

Changes

Cohort / File(s) Summary
Documentation & Props
apps/www/src/content/docs/components/breadcrumb/index.mdx, apps/www/src/content/docs/components/breadcrumb/props.ts
Updated to document the new render prop replacing as, including ref forwarding behavior, prop merging semantics, and updated dropdown-items precedence rules.
Demo & Examples
apps/www/src/content/docs/components/breadcrumb/demo.ts, apps/www/src/components/playground/breadcrumb-examples.tsx
Updated example usage to use render prop instead of as prop for custom link elements.
Breadcrumb Item Implementation
packages/raystack/components/breadcrumb/breadcrumb-item.tsx
Replaced as prop with render prop using useRender.ComponentProps; refactored rendering logic with mergeProps for prop composition; added dedicated current branch rendering <span> with aria-current="page"; updated label composition and dropdown trigger attributes.
Breadcrumb Root Enhancement
packages/raystack/components/breadcrumb/breadcrumb-root.tsx
Added ref forwarding to <nav> element and optional ariaLabel prop with default fallback to 'Breadcrumb'.
Test Updates
packages/raystack/components/breadcrumb/__tests__/breadcrumb.test.tsx
Updated active state test to assert <span> rendering; replaced as prop test with render prop test using anchor element; added ref forwarding verification test for render prop.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • feat: migrate misc components #594 — Performs equivalent API migration replacing "as"/"asChild" with "render" prop and adopting useRender/mergeProps-based rendering across components.
  • feat: React 19 migration - 1 #708 — Modifies breadcrumb components with analogous polymorphic prop refactoring (as → render), ref forwarding, and prop type changes.

Suggested reviewers

  • rsbh
  • rohanchkrabrty
  • rohilsurana

Poem

🐰✨ From as to render, our breadcrumbs now shine,
With useRender hooks that align line by line,
Props merge so sweetly, refs forward with grace,
A newer, cleaner, more elegant place!
Hops happily 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main breaking change: replacing cloneElement approach with composition and removing the as prop in favor of render, which is the primary focus across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/breadcrumb-composition
📝 Coding Plan
  • Generate coding plan for human review comments

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
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/raystack/components/breadcrumb/breadcrumb-item.tsx (1)

15-20: ⚠️ Potential issue | 🟠 Major

The new polymorphic contract is only implemented at runtime.

BreadcrumbItemProps is still fixed to ComponentProps<'a'>, so as={NextLink} will still reject component-specific props even though the docs now say they pass through. The same anchor-shaped ref/props are then reused for the dropdownItems button branch (lines 48–50) and the current span branch (lines 73–79), which cast the ref and spread anchor props onto elements that don't match. This lets onClick, tabIndex, and other anchor-specific props leak onto the "non-clickable" current item, and allows aria-current to be overridden by the props spread. The as parameter only takes effect in the default link branch (lines 88–93), not in the dropdown or current branches. This needs a polymorphic type for the as path and narrowed prop/ref surfaces for the non-link branches.

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

In `@packages/raystack/components/breadcrumb/breadcrumb-item.tsx` around lines 15
- 20, BreadcrumbItemProps is currently tied to ComponentProps<'a'> so the
polymorphic as prop isn't typed and anchor props/refs are leaking into non-link
branches (dropdownItems button and current span) — change BreadcrumbItemProps
into a proper polymorphic generic (e.g., BreadcrumbItemProps<E extends
ElementType = 'a'>) and derive props/ref via ComponentPropsWithRef<E> or
ComponentPropsWithoutRef<E> for the link branch; create narrowed discriminated
unions or separate prop subsets for the dropdown branch (use correct button
HTMLAttributes/Ref<HTMLButtonElement>) and the current branch (span
props/Ref<HTMLSpanElement>) so you stop spreading anchor-specific props and refs
onto non-anchor elements, keep the as prop and default 'a' for the generic, and
update the render branches that reference dropdownItems, current and as to use
the appropriately-typed props/refs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/raystack/components/breadcrumb/breadcrumb-root.tsx`:
- Around line 23-36: BreadcrumbRoot currently accepts a ref prop and passes it
directly to the <nav>, which breaks ref forwarding for React 18; wrap the
component with React.forwardRef and change the signature to accept (props, ref)
so the DOM ref is forwarded to the <nav> element. Update the export to use
forwardRef(BreadcrumbRoot) (or define const BreadcrumbRoot = forwardRef(function
BreadcrumbRoot(props, ref) { ... })), keep using breadcrumbVariants,
BreadcrumbProps and ariaLabel as before, and ensure the forwarded ref is passed
to the nav element and prop types updated if needed.

---

Outside diff comments:
In `@packages/raystack/components/breadcrumb/breadcrumb-item.tsx`:
- Around line 15-20: BreadcrumbItemProps is currently tied to
ComponentProps<'a'> so the polymorphic as prop isn't typed and anchor props/refs
are leaking into non-link branches (dropdownItems button and current span) —
change BreadcrumbItemProps into a proper polymorphic generic (e.g.,
BreadcrumbItemProps<E extends ElementType = 'a'>) and derive props/ref via
ComponentPropsWithRef<E> or ComponentPropsWithoutRef<E> for the link branch;
create narrowed discriminated unions or separate prop subsets for the dropdown
branch (use correct button HTMLAttributes/Ref<HTMLButtonElement>) and the
current branch (span props/Ref<HTMLSpanElement>) so you stop spreading
anchor-specific props and refs onto non-anchor elements, keep the as prop and
default 'a' for the generic, and update the render branches that reference
dropdownItems, current and as to use the appropriately-typed props/refs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7d72d9e-a73e-402b-9e47-05d7505683b7

📥 Commits

Reviewing files that changed from the base of the PR and between f77adb8 and 1fc3160.

📒 Files selected for processing (7)
  • apps/www/src/components/playground/index.ts
  • apps/www/src/content/docs/components/breadcrumb/demo.ts
  • apps/www/src/content/docs/components/breadcrumb/index.mdx
  • apps/www/src/content/docs/components/breadcrumb/props.ts
  • packages/raystack/components/breadcrumb/__tests__/breadcrumb.test.tsx
  • packages/raystack/components/breadcrumb/breadcrumb-item.tsx
  • packages/raystack/components/breadcrumb/breadcrumb-root.tsx

Copy link
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: 4

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

Inline comments:
In `@apps/www/src/content/docs/components/breadcrumb/index.mdx`:
- Around line 95-102: The example uses the element form render={<NextLink />}
which fails TypeScript because Next.js Link requires href; update the docs and
example for Breadcrumb.Item (component Breadcrumb.Item, prop render) to use the
function form so props are forwarded at render time (render={(props) =>
<NextLink {...props} />}) or else show the element form with an explicit href
(render={<NextLink href="/" />}); change the example text and code snippet
accordingly to recommend the callback form for components with required props
and include NextLink/ Breadcrumb.Item references.

In `@packages/raystack/components/breadcrumb/breadcrumb-item.tsx`:
- Around line 42-48: The destructuring in BreadcrumbItem currently pulls only
id, title and a few aria-* fields and drops all other props (so data-*,
tabIndex, onClick, etc. never reach Menu.Trigger); change the destructuring to
capture the rest (e.g. add ...rest to const { id, title, 'aria-label':
ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby':
ariaDescribedby, ...rest } = props) and spread {...rest} onto Menu.Trigger (and
the analogous element in the other code path mentioned at lines 68-75) so
data-*, tabIndex and consumer handlers are forwarded unchanged to the trigger
element.
- Around line 98-105: The span currently applies aria-current='page' and then
spreads {...props}, which allows callers to override/remove aria-current or
reattach interactive handlers; fix by filtering incoming props (the `props`
object passed to the span) to a small whitelist of neutral attributes (e.g.,
className, id, data-* and non-interactive aria-* except aria-current) or
explicitly omit interactive props like onClick, tabIndex, role, href, and then
spread the filteredProps before setting aria-current; update the JSX in
breadcrumb-item.tsx (the span with ref, cx and styles['breadcrumb-link'] /
styles['breadcrumb-link-active']) so that filteredProps are spread first and
aria-current='page' is set last to guarantee the page semantics can't be
overridden.
- Line 16: BreadcrumbItemProps currently extends useRender.ComponentProps<'a'>
(anchor ref) but the component sometimes renders Menu.Trigger (button) or a
plain <span>, causing ref type mismatches; fix by making the props/ref contract
match render variants: either (A) split BreadcrumbItem into distinct variants
with a discriminated prop (e.g. kind: 'link' | 'dropdown' | 'current') and
define BreadcrumbItemProps as a union where the 'link' variant extends
useRender.ComponentProps<'a'> (ref: HTMLAnchorElement) while 'dropdown' extends
React.ComponentPropsWithRef<'button'> (ref: HTMLButtonElement) and 'current'
omits forwarded ref or uses React.Ref<HTMLSpanElement); or (B) remove ref
forwarding from non-anchor branches by only forwarding the ref to the anchor
path in the BreadcrumbItem forwardRef implementation and use internal refs for
Menu.Trigger and the <span>, updating usages of Menu.Trigger and the current
span render path accordingly; adjust type declarations and the BreadcrumbItem
forwardRef signature to reflect the chosen approach so consumers never receive
an anchor ref for a non-anchor element.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d825f004-70d3-4e50-a27c-d3431fc0b4c9

📥 Commits

Reviewing files that changed from the base of the PR and between 1fc3160 and 4481c5e.

📒 Files selected for processing (6)
  • apps/www/src/components/playground/breadcrumb-examples.tsx
  • apps/www/src/content/docs/components/breadcrumb/demo.ts
  • apps/www/src/content/docs/components/breadcrumb/index.mdx
  • apps/www/src/content/docs/components/breadcrumb/props.ts
  • packages/raystack/components/breadcrumb/__tests__/breadcrumb.test.tsx
  • packages/raystack/components/breadcrumb/breadcrumb-item.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/www/src/components/playground/breadcrumb-examples.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/www/src/content/docs/components/breadcrumb/demo.ts
  • apps/www/src/content/docs/components/breadcrumb/props.ts

Comment on lines +95 to +102
### Render

Use the `as` prop to render the breadcrumb item as a custom component. By default, breadcrumb items are rendered as `a` tags.
Use the `render` prop to render the breadcrumb item as a custom component. Pass a JSX element (e.g. `render={<NextLink />}`) and we will replace the default `<a>` while merging props. By default, items render as `a` tags.

When a custom component is provided, the props are merged, with the custom component's props taking precedence over the breadcrumb item's props.
```tsx
// Correct: set href on the item (it will be forwarded into the rendered element)
<Breadcrumb.Item href="/" render={<NextLink />}>Home</Breadcrumb.Item>
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Base UI render prop prop merging behavior

💡 Result:

Base UI's render prop uses intelligent prop merging via the mergeProps utility to combine component props with user-provided render props. Props are merged left-to-right (rightmost wins for most keys), with special handling for className (concatenated right-to-left), style (deep merged, rightmost overwrites), event handlers (merged and executed right-to-left), and ref (rightmost ref kept). This ensures custom components receive Base UI's behavioral props (like event handlers, data attributes) while preserving user styles and handlers. In render prop usage: - Element form: <Component render={} /> - mergeProps automatically combines props. - Function form: render={(props) => <Custom {...props} />} or render={(props, state) => ...} - use mergeProps manually for merging, e.g., <Custom {...mergeProps(internalProps, userProps)} />. Call event.preventBaseUIHandler to prevent Base UI's internal logic. The useRender hook implements render prop logic, merging props before spreading on the rendered element.

Citations:


🌐 Web query:

Next.js Link component href required prop

💡 Result:

Yes, the href prop is required for the Next.js Link component in both App Router and Pages Router.

Citations:


🏁 Script executed:

# Check the actual file and context around the example
fd -p "breadcrumb/index.mdx" apps/www/src/content/docs/components/

Repository: raystack/apsara

Length of output: 116


🏁 Script executed:

# Read the actual markdown file to see the context and full example
cat -n apps/www/src/content/docs/components/breadcrumb/index.mdx | sed -n '90,110p'

Repository: raystack/apsara

Length of output: 981


🏁 Script executed:

# Check if there are type definitions or examples elsewhere for breadcrumb
fd -p "breadcrumb" apps/www/src --type f | head -20

Repository: raystack/apsara

Length of output: 288


🏁 Script executed:

# Check the breadcrumb component implementation
cat apps/www/src/components/playground/breadcrumb-examples.tsx

Repository: raystack/apsara

Length of output: 1127


🏁 Script executed:

# Check the demo file to see if there are live examples
cat apps/www/src/content/docs/components/breadcrumb/demo.ts

Repository: raystack/apsara

Length of output: 4341


🏁 Script executed:

# Check the actual Breadcrumb.Item component implementation to understand prop forwarding
fd -p "breadcrumb" apps/www --type f -e "ts" -e "tsx" | xargs grep -l "Breadcrumb.Item\|render" | head -5

Repository: raystack/apsara

Length of output: 230


🏁 Script executed:

# Find the main component source files
fd -p "breadcrumb" --type f -e "ts" -e "tsx" | grep -v "node_modules\|dist" | head -20

Repository: raystack/apsara

Length of output: 585


🏁 Script executed:

# Check the Breadcrumb.Item component implementation
cat packages/raystack/components/breadcrumb/breadcrumb-item.tsx

Repository: raystack/apsara

Length of output: 3017


🏁 Script executed:

# Also check the main breadcrumb file
cat packages/raystack/components/breadcrumb/breadcrumb.tsx

Repository: raystack/apsara

Length of output: 391


🏁 Script executed:

# Check the types used by useRender to understand render prop expectations
rg "useRender" packages/raystack/components/breadcrumb/ -A 5

Repository: raystack/apsara

Length of output: 1712


🏁 Script executed:

# Check if there are any type definitions or interfaces for the render prop
rg "ComponentProps" packages/raystack/components/breadcrumb/ -B 2 -A 2

Repository: raystack/apsara

Length of output: 2677


🏁 Script executed:

# Check if there's any tsconfig or type relaxations that might allow this
fd -p "tsconfig" --type f

Repository: raystack/apsara

Length of output: 233


🏁 Script executed:

# Look for actual uses of NextLink in the codebase to see how they handle it
rg "NextLink" --type tsx --type ts -A 2 -B 2 | head -50

Repository: raystack/apsara

Length of output: 86


🏁 Script executed:

# Look for actual uses of NextLink in the codebase
rg "NextLink" -A 2 -B 2 | head -80

Repository: raystack/apsara

Length of output: 2773


🏁 Script executed:

# Check the tsconfig to see if there's any type relaxation
cat apps/www/tsconfig.json

Repository: raystack/apsara

Length of output: 720


🏁 Script executed:

# Check the props.ts file to see the exact type definition for render
cat apps/www/src/content/docs/components/breadcrumb/props.ts

Repository: raystack/apsara

Length of output: 1689


🏁 Script executed:

# Try to understand how the render prop type is defined - check if it allows optional props
rg "render\?" packages/raystack/components/breadcrumb/ -A 1 -B 1

Repository: raystack/apsara

Length of output: 41


🏁 Script executed:

# Check Next.js Link type definition to confirm href is required
rg "export.*Link" node_modules/next/link.d.ts -A 10 | head -30

Repository: raystack/apsara

Length of output: 126


🏁 Script executed:

# Check if there are any tests showing how render prop is used
cat packages/raystack/components/breadcrumb/__tests__/breadcrumb.test.tsx | head -100

Repository: raystack/apsara

Length of output: 2916


Use the function form of the render prop for components with required props.

The example render={<NextLink />} will fail TypeScript type-checking because Next.js Link requires the href prop. Base UI's prop merging happens at runtime, after JSX evaluation. Use the callback form instead: render={(props) => <NextLink {...props} />}. If you want to keep the element form, explicitly provide href: render={<NextLink href="/" />}.

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

In `@apps/www/src/content/docs/components/breadcrumb/index.mdx` around lines 95 -
102, The example uses the element form render={<NextLink />} which fails
TypeScript because Next.js Link requires href; update the docs and example for
Breadcrumb.Item (component Breadcrumb.Item, prop render) to use the function
form so props are forwarded at render time (render={(props) => <NextLink
{...props} />}) or else show the element form with an explicit href
(render={<NextLink href="/" />}); change the example text and code snippet
accordingly to recommend the callback form for components with required props
and include NextLink/ Breadcrumb.Item references.

}

export interface BreadcrumbItemProps extends ComponentProps<'a'> {
export interface BreadcrumbItemProps extends useRender.ComponentProps<'a'> {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/raystack/components/breadcrumb/breadcrumb-item.tsx

Repository: raystack/apsara

Length of output: 3836


Fix the ref type contract across all render paths in BreadcrumbItem.

BreadcrumbItemProps extends useRender.ComponentProps<'a'>, declaring an anchor-typed ref. However, the dropdown branch at line 69 casts the ref to HTMLButtonElement (used in Menu.Trigger), and the current branch at line 99 casts to HTMLSpanElement (used in <span>). Consumers can pass an anchor-oriented ref and receive a different element type at runtime.

Either split the component variants to have distinct ref types for each case, or remove ref forwarding from the non-anchor branches.

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

In `@packages/raystack/components/breadcrumb/breadcrumb-item.tsx` at line 16,
BreadcrumbItemProps currently extends useRender.ComponentProps<'a'> (anchor ref)
but the component sometimes renders Menu.Trigger (button) or a plain <span>,
causing ref type mismatches; fix by making the props/ref contract match render
variants: either (A) split BreadcrumbItem into distinct variants with a
discriminated prop (e.g. kind: 'link' | 'dropdown' | 'current') and define
BreadcrumbItemProps as a union where the 'link' variant extends
useRender.ComponentProps<'a'> (ref: HTMLAnchorElement) while 'dropdown' extends
React.ComponentPropsWithRef<'button'> (ref: HTMLButtonElement) and 'current'
omits forwarded ref or uses React.Ref<HTMLSpanElement); or (B) remove ref
forwarding from non-anchor branches by only forwarding the ref to the anchor
path in the BreadcrumbItem forwardRef implementation and use internal refs for
Menu.Trigger and the <span>, updating usages of Menu.Trigger and the current
span render path accordingly; adjust type declarations and the BreadcrumbItem
forwardRef signature to reflect the chosen approach so consumers never receive
an anchor ref for a non-anchor element.

Comment on lines +42 to +48
const {
id,
title,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledby,
'aria-describedby': ariaDescribedby
} = props;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Dropdown trigger props are being dropped.

This whitelist only forwards id, title, and a few aria-* fields into Menu.Trigger, so data-*, tabIndex, and consumer handlers like onClick never reach the trigger when dropdownItems is set. That regresses the normal prop-forwarding contract and makes the dropdown path behave differently from the link path.

Also applies to: 68-75

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

In `@packages/raystack/components/breadcrumb/breadcrumb-item.tsx` around lines 42
- 48, The destructuring in BreadcrumbItem currently pulls only id, title and a
few aria-* fields and drops all other props (so data-*, tabIndex, onClick, etc.
never reach Menu.Trigger); change the destructuring to capture the rest (e.g.
add ...rest to const { id, title, 'aria-label': ariaLabel, 'aria-labelledby':
ariaLabelledby, 'aria-describedby': ariaDescribedby, ...rest } = props) and
spread {...rest} onto Menu.Trigger (and the analogous element in the other code
path mentioned at lines 68-75) so data-*, tabIndex and consumer handlers are
forwarded unchanged to the trigger element.

Comment on lines +98 to +105
<span
ref={ref as React.RefObject<HTMLSpanElement>}
className={cx(
styles['breadcrumb-link'],
current && styles['breadcrumb-link-active']
),
href,
...props,
...renderedElement.props,
ref
},
label
)}
</li>
styles['breadcrumb-link-active']
)}
aria-current='page'
{...props}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

current can be stripped of its page semantics.

Because {...props} is spread after aria-current='page', callers can override/remove the current-page state and also reattach interactive props like onClick or tabIndex to what is supposed to be a non-link page indicator. This branch should whitelist neutral attributes and set aria-current last.

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

In `@packages/raystack/components/breadcrumb/breadcrumb-item.tsx` around lines 98
- 105, The span currently applies aria-current='page' and then spreads
{...props}, which allows callers to override/remove aria-current or reattach
interactive handlers; fix by filtering incoming props (the `props` object passed
to the span) to a small whitelist of neutral attributes (e.g., className, id,
data-* and non-interactive aria-* except aria-current) or explicitly omit
interactive props like onClick, tabIndex, role, href, and then spread the
filteredProps before setting aria-current; update the JSX in breadcrumb-item.tsx
(the span with ref, cx and styles['breadcrumb-link'] /
styles['breadcrumb-link-active']) so that filteredProps are spread first and
aria-current='page' is set last to guarantee the page semantics can't be
overridden.

@ravisuhag ravisuhag linked an issue Mar 20, 2026 that may be closed by this pull request
@paanSinghCoder paanSinghCoder merged commit 720ee57 into main Mar 23, 2026
5 checks passed
@paanSinghCoder paanSinghCoder deleted the fix/breadcrumb-composition branch March 23, 2026 06:13
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.

[Breadcrumb] Structural improvements and feature enhancements

2 participants