diff --git a/apps/www/src/content/docs/components/breadcrumb/demo.ts b/apps/www/src/content/docs/components/breadcrumb/demo.ts
index 866a05108..eafd22f3e 100644
--- a/apps/www/src/content/docs/components/breadcrumb/demo.ts
+++ b/apps/www/src/content/docs/components/breadcrumb/demo.ts
@@ -98,9 +98,9 @@ export const asDemo = {
type: 'code',
code: `
- }>Home
+ Home
- }>Playground
+ Playground
Docs
`
diff --git a/apps/www/src/content/docs/components/breadcrumb/index.mdx b/apps/www/src/content/docs/components/breadcrumb/index.mdx
index 0b2449853..e14bfc29b 100644
--- a/apps/www/src/content/docs/components/breadcrumb/index.mdx
+++ b/apps/www/src/content/docs/components/breadcrumb/index.mdx
@@ -42,7 +42,7 @@ Groups all parts of the breadcrumb navigation.
### Item
-Renders an individual breadcrumb link.
+Renders an individual breadcrumb link. Ref is forwarded to the link or custom component when using `as` (not when using `dropdownItems`).
@@ -94,9 +94,12 @@ Breadcrumb items can include dropdown menus for additional navigation options. S
### As
-Use the `as` prop to render the breadcrumb item as a custom component. By default, breadcrumb items are rendered as `a` tags.
+Use the `as` prop to render the breadcrumb item as a custom component. Pass the **component reference** (e.g. `as={NextLink}`), not a JSX element. Put `href` and other props on `Breadcrumb.Item`; they are passed through to the component. 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: pass the component, set href on the item
+Home
+```
diff --git a/apps/www/src/content/docs/components/breadcrumb/props.ts b/apps/www/src/content/docs/components/breadcrumb/props.ts
index 02b7d4ed9..6507786fa 100644
--- a/apps/www/src/content/docs/components/breadcrumb/props.ts
+++ b/apps/www/src/content/docs/components/breadcrumb/props.ts
@@ -1,4 +1,4 @@
-import { ReactElement, ReactEventHandler, ReactNode } from 'react';
+import { ElementType, ReactEventHandler, ReactNode } from 'react';
export interface BreadcrumbItem {
/** Text to display for the item */
@@ -29,13 +29,12 @@ export interface BreadcrumbItem {
}[];
/**
- * Custom element used to render the Item.
+ * Component to render as (e.g. NextLink). Pass the component reference, not a JSX element.
+ * Receives breadcrumb item props (href, className, ref, children, etc.).
*
- * All props are merged, with the custom component's props taking precedence over the breadcrumb item's props.
- *
- * @default ""
+ * @default "a"
*/
- as?: ReactElement;
+ as?: ElementType;
}
export interface BreadcrumbProps {
diff --git a/packages/raystack/components/breadcrumb/__tests__/breadcrumb.test.tsx b/packages/raystack/components/breadcrumb/__tests__/breadcrumb.test.tsx
index 95b9587d7..ce49aa652 100644
--- a/packages/raystack/components/breadcrumb/__tests__/breadcrumb.test.tsx
+++ b/packages/raystack/components/breadcrumb/__tests__/breadcrumb.test.tsx
@@ -1,4 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react';
+import React, { forwardRef } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { Breadcrumb } from '../breadcrumb';
import styles from '../breadcrumb.module.css';
@@ -135,24 +136,32 @@ describe('Breadcrumb', () => {
);
- const link = container.querySelector('a');
- expect(link).toHaveClass(styles['breadcrumb-link-active']);
+ // Current page renders as (non-link), not
+ const currentEl = container.querySelector(
+ `span.${styles['breadcrumb-link-active']}`
+ );
+ expect(currentEl).toBeInTheDocument();
+ expect(currentEl).toHaveAttribute('aria-current', 'page');
});
it('renders with custom element using as prop', () => {
- const CustomLink = ({ children, ...props }: any) => (
-
+ const CustomLink: React.ComponentType<
+ React.AnchorHTMLAttributes
+ > = ({ children, ...props }) => (
+
+ {children}
+
);
const { container } = render(
- }>Custom
+ Custom
);
- const button = container.querySelector('button');
- expect(button).toBeInTheDocument();
- expect(button).toHaveClass(styles['breadcrumb-link']);
+ const link = container.querySelector('[data-custom-link]');
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveClass(styles['breadcrumb-link']);
expect(screen.getByText('Custom')).toBeInTheDocument();
});
@@ -166,6 +175,26 @@ describe('Breadcrumb', () => {
expect(ref).toHaveBeenCalled();
});
+ it('forwards ref when using as prop (component)', () => {
+ const CustomLink = forwardRef<
+ HTMLAnchorElement,
+ React.AnchorHTMLAttributes
+ >(({ children, ...props }, ref) => (
+
+ {children}
+
+ ));
+ const ref = vi.fn();
+ render(
+
+
+ Custom
+
+
+ );
+ expect(ref).toHaveBeenCalled();
+ });
+
it('applies custom className', () => {
const { container } = render(
diff --git a/packages/raystack/components/breadcrumb/breadcrumb-item.tsx b/packages/raystack/components/breadcrumb/breadcrumb-item.tsx
index 0b3471ac2..0a8fc1a30 100644
--- a/packages/raystack/components/breadcrumb/breadcrumb-item.tsx
+++ b/packages/raystack/components/breadcrumb/breadcrumb-item.tsx
@@ -2,13 +2,7 @@
import { ChevronDownIcon } from '@radix-ui/react-icons';
import { cx } from 'class-variance-authority';
-import React, {
- cloneElement,
- forwardRef,
- HTMLAttributes,
- ReactElement,
- ReactNode
-} from 'react';
+import React, { forwardRef, HTMLAttributes, ReactNode } from 'react';
import { Menu } from '../menu';
import styles from './breadcrumb.module.css';
@@ -22,7 +16,8 @@ export interface BreadcrumbItemProps extends HTMLAttributes {
current?: boolean;
dropdownItems?: BreadcrumbDropdownItem[];
href?: string;
- as?: ReactElement;
+ /** Component to render as (e.g. NextLink). Receives our props (href, className, ref, etc.). Defaults to "a". */
+ as?: React.ElementType;
}
export const BreadcrumbItem = forwardRef<
@@ -42,14 +37,15 @@ export const BreadcrumbItem = forwardRef<
},
ref
) => {
- const renderedElement = as ?? ;
- const label = (
+ const Component = as ?? 'a';
+ // Only wrap in spans when needed for layout (leading icon); otherwise keep link content as plain text so DOM shows Home not Home
+ const label = leadingIcon ? (
<>
- {leadingIcon && (
- {leadingIcon}
- )}
- {children && {children}}
+ {leadingIcon}
+ {children != null && {children}}
>
+ ) : (
+ children
);
if (dropdownItems) {
@@ -73,21 +69,35 @@ export const BreadcrumbItem = forwardRef<
);
}
+ // Current page: render as non-link (semantic; no href)
+ if (current) {
+ return (
+
+ }
+ className={cx(
+ styles['breadcrumb-link'],
+ styles['breadcrumb-link-active']
+ )}
+ aria-current='page'
+ {...props}
+ >
+ {label}
+
+
+ );
+ }
+ // Link: render as or custom Component (e.g. NextLink → in DOM)
return (
- {cloneElement(
- renderedElement,
- {
- className: cx(
- styles['breadcrumb-link'],
- current && styles['breadcrumb-link-active']
- ),
- href,
- ...props,
- ...renderedElement.props
- },
- label
- )}
+
+ {label}
+
);
}
diff --git a/packages/raystack/components/breadcrumb/breadcrumb-root.tsx b/packages/raystack/components/breadcrumb/breadcrumb-root.tsx
index 2fdded41e..0e4705b96 100644
--- a/packages/raystack/components/breadcrumb/breadcrumb-root.tsx
+++ b/packages/raystack/components/breadcrumb/breadcrumb-root.tsx
@@ -1,7 +1,7 @@
'use client';
-import { type VariantProps, cva } from 'class-variance-authority';
-import { HTMLAttributes, forwardRef } from 'react';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { forwardRef, HTMLAttributes } from 'react';
import styles from './breadcrumb.module.css';
const breadcrumbVariants = cva(styles['breadcrumb'], {
@@ -21,11 +21,15 @@ export interface BreadcrumbProps
HTMLAttributes {}
export const BreadcrumbRoot = forwardRef(
- ({ className, children, size = 'medium', ...props }, ref) => {
+ (
+ { className, children, size = 'medium', 'aria-label': ariaLabel, ...props },
+ ref
+ ) => {
return (