diff --git a/apps/www/src/components/playground/breadcrumb-examples.tsx b/apps/www/src/components/playground/breadcrumb-examples.tsx
index 9d9fbb7f1..86e4c8f7c 100644
--- a/apps/www/src/components/playground/breadcrumb-examples.tsx
+++ b/apps/www/src/components/playground/breadcrumb-examples.tsx
@@ -29,7 +29,7 @@ export function BreadcrumbExamples() {
Products
- } current>
+ } current>
Shoes
diff --git a/apps/www/src/content/docs/components/breadcrumb/demo.ts b/apps/www/src/content/docs/components/breadcrumb/demo.ts
index 866a05108..e2d5948d8 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..63a53ae4f 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 rendered element when using `render` (not when using `dropdownItems`).
@@ -88,15 +88,18 @@ Breadcrumb items can include icons either alongside text or as standalone elemen
Breadcrumb items can include dropdown menus for additional navigation options. Specify the dropdown items using the `dropdownItems` prop.
-**Note:** When `dropdownItems` is provided, the `as` and `href` props are ignored.
+**Note:** When `dropdownItems` is provided, the `render` and `href` props are ignored.
-### As
+### 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={}`) and we will replace the default `` 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)
+}>Home
+```
diff --git a/apps/www/src/content/docs/components/breadcrumb/props.ts b/apps/www/src/content/docs/components/breadcrumb/props.ts
index fd6ea6895..ba334786f 100644
--- a/apps/www/src/content/docs/components/breadcrumb/props.ts
+++ b/apps/www/src/content/docs/components/breadcrumb/props.ts
@@ -19,7 +19,7 @@ export interface BreadcrumbItem {
/**
* Optional array of dropdown items
*
- * When `dropdownItems` is provided, the `as` and `href` props are ignored.
+ * When `dropdownItems` is provided, the `render` and `href` props are ignored.
*/
dropdownItems?: {
/** Optional stable key for list reconciliation. Falls back to index if omitted. */
@@ -31,13 +31,12 @@ export interface BreadcrumbItem {
}[];
/**
- * Custom element used to render the Item.
+ * Render prop for polymorphism (Base UI `useRender`).
+ * Pass a JSX element to replace the default rendered ``.
*
- * All props are merged, with the custom component's props taking precedence over the breadcrumb item's props.
- *
- * @default ""
+ * Example: `render={}`
*/
- as?: ReactElement;
+ render?: ReactElement;
}
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..9f4282f1f 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) => (
-
+ it('renders with custom element using render prop', () => {
+ 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 render 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 7a993b0c9..cac155543 100644
--- a/packages/raystack/components/breadcrumb/breadcrumb-item.tsx
+++ b/packages/raystack/components/breadcrumb/breadcrumb-item.tsx
@@ -1,13 +1,9 @@
'use client';
+import { mergeProps, useRender } from '@base-ui/react';
import { ChevronDownIcon } from '@radix-ui/react-icons';
import { cx } from 'class-variance-authority';
-import React, {
- ComponentProps,
- cloneElement,
- ReactElement,
- ReactNode
-} from 'react';
+import React, { ReactNode } from 'react';
import { Menu } from '../menu';
import styles from './breadcrumb.module.css';
@@ -17,16 +13,15 @@ export interface BreadcrumbDropdownItem {
onClick?: React.MouseEventHandler;
}
-export interface BreadcrumbItemProps extends ComponentProps<'a'> {
+export interface BreadcrumbItemProps extends useRender.ComponentProps<'a'> {
leadingIcon?: ReactNode;
current?: boolean;
dropdownItems?: BreadcrumbDropdownItem[];
- as?: ReactElement;
}
export const BreadcrumbItem = ({
ref,
- as,
+ render,
children,
className,
leadingIcon,
@@ -35,6 +30,15 @@ export const BreadcrumbItem = ({
dropdownItems,
...props
}: BreadcrumbItemProps) => {
+ const label = leadingIcon ? (
+ <>
+ {leadingIcon}
+ {children != null && {children}}
+ >
+ ) : (
+ children
+ );
+
const {
id,
title,
@@ -43,15 +47,19 @@ export const BreadcrumbItem = ({
'aria-describedby': ariaDescribedby
} = props;
- const renderedElement = as ?? ;
- const label = (
- <>
- {leadingIcon && (
- {leadingIcon}
- )}
- {children && {children}}
- >
- );
+ const linkElement = useRender({
+ defaultTagName: 'a',
+ ref,
+ render,
+ props: mergeProps<'a'>(
+ {
+ className: cx(styles['breadcrumb-link']),
+ href,
+ children: label
+ },
+ props
+ )
+ });
if (dropdownItems) {
return (
@@ -84,23 +92,25 @@ export const BreadcrumbItem = ({
);
}
- return (
-
- {cloneElement(
- renderedElement,
- {
- className: cx(
+ if (current) {
+ return (
+
+ }
+ className={cx(
styles['breadcrumb-link'],
- current && styles['breadcrumb-link-active']
- ),
- href,
- ...props,
- ...renderedElement.props,
- ref
- },
- label
- )}
-
+ styles['breadcrumb-link-active']
+ )}
+ aria-current='page'
+ {...props}
+ >
+ {label}
+
+
+ );
+ }
+ return (
+
{linkElement}
);
};
diff --git a/packages/raystack/components/breadcrumb/breadcrumb-root.tsx b/packages/raystack/components/breadcrumb/breadcrumb-root.tsx
index 3bcf4ae9c..a348cdc13 100644
--- a/packages/raystack/components/breadcrumb/breadcrumb-root.tsx
+++ b/packages/raystack/components/breadcrumb/breadcrumb-root.tsx
@@ -23,11 +23,18 @@ export interface BreadcrumbProps
export const BreadcrumbRoot = ({
className,
children,
+ ref,
size = 'medium',
+ 'aria-label': ariaLabel,
...props
}: BreadcrumbProps) => {
return (
-