Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/www/src/app/examples/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Amount,
Avatar,
AvatarGroup,
Breadcrumb,
Button,
Calendar,
Callout,
Expand Down
2 changes: 2 additions & 0 deletions apps/www/src/components/demo/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
BellIcon,
FilterIcon,
OrganizationIcon,
ShoppingBagFilledIcon,
SidebarIcon
} from '@raystack/apsara/icons';
import dayjs from 'dayjs';
Expand All @@ -38,6 +39,7 @@ export default function Demo(props: DemoProps) {
...Apsara,
BellIcon,
FilterIcon,
ShoppingBagFilledIcon,
OrganizationIcon,
SidebarIcon,
DataTableDemo,
Expand Down
4 changes: 2 additions & 2 deletions apps/www/src/components/playground/breadcrumb-examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ export function BreadcrumbExamples() {
<Breadcrumb.Item
dropdownItems={[
{
label: 'Clothes',
children: 'Clothes',
onClick: () => {
console.log('Clothes');
}
},
{
label: 'Electronics',
children: 'Electronics',
onClick: () => {
console.log('Electronics');
}
Expand Down
110 changes: 84 additions & 26 deletions apps/www/src/content/docs/components/breadcrumb/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,30 @@ export const playground = {

export const sizeDemo = {
type: 'code',
code: `
<Flex gap="medium" direction="column">
<Breadcrumb size="small">
<Breadcrumb.Item href="/">Home</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/products">Products</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/products/shoes" current>Shoes</Breadcrumb.Item>
</Breadcrumb>
<Breadcrumb size="medium">
<Breadcrumb.Item href="/">Home</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/products">Products</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/products/shoes" current>Shoes</Breadcrumb.Item>
</Breadcrumb>
</Flex>`
tabs: [
{
name: 'Small',
code: `
<Breadcrumb size="small">
<Breadcrumb.Item href="/">Home</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/products">Products</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/products/shoes" current>Shoes</Breadcrumb.Item>
</Breadcrumb>`
},
{
name: 'Medium',
code: `
<Breadcrumb size="medium">
<Breadcrumb.Item href="/">Home</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/products">Products</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/products/shoes" current>Shoes</Breadcrumb.Item>
</Breadcrumb>`
}
]
};

export const separatorDemo = {
Expand Down Expand Up @@ -87,13 +94,30 @@ export const dropdownDemo = {
<Breadcrumb.Item href="/category">Category</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item dropdownItems={[
{ label: 'Option 1', onClick: () => {console.log('Option 1')}},
{ label: 'Option 2', onClick: () => {console.log('Option 2')}}
{ children: 'Option 1', onClick: () => {console.log('Option 1')}},
{ children: 'Option 2', onClick: () => {console.log('Option 2')}}
]}>Subcategory</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/category/subcategory/current">Current Page</Breadcrumb.Item>
</Breadcrumb>`
};

export const dropdownLinksDemo = {
type: 'code',
code: `
<Breadcrumb>
<Breadcrumb.Item href="/">Home</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item dropdownItems={[
{ children: 'Electronics', render: <a href="/electronics" target="_blank" rel="noopener noreferrer" /> },
{ children: 'Clothing', render: <a href="/clothing" /> },
{ children: 'Books', onClick: () => {console.log('Books')}}
]}>Categories</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/current" current>Current</Breadcrumb.Item>
</Breadcrumb>`
};

export const asDemo = {
type: 'code',
code: `
Expand All @@ -106,29 +130,63 @@ export const asDemo = {
</Breadcrumb>`
};

export const disabledDemo = {
type: 'code',
code: `
<Breadcrumb>
<Breadcrumb.Item href="/">Home</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item disabled>Loading…</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/products" current>Products</Breadcrumb.Item>
</Breadcrumb>`
};

export const iconsDemo = {
type: 'code',
tabs: [
{
name: 'Text with Icon',
name: 'Leading Icon',
code: `
<Breadcrumb>
<Breadcrumb.Item href="/" leadingIcon={<BellIcon />}>Home</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/documents" leadingIcon={<FilterIcon />}>Documents</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/settings" leadingIcon={<ShoppingBagFilledIcon />}>Settings</Breadcrumb.Item>
</Breadcrumb>`
},
{
name: 'Trailing Icon',
code: `
<Breadcrumb>
<Breadcrumb.Item href="/" trailingIcon={<BellIcon />}>Home</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/documents" trailingIcon={<FilterIcon />}>Documents</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/settings" trailingIcon={<ShoppingBagFilledIcon />}>Settings</Breadcrumb.Item>
</Breadcrumb>`
},
{
name: 'Both Icons',
code: `
<Breadcrumb>
<Breadcrumb.Item href="/" leadingIcon={<>H</>}>Home</Breadcrumb.Item>
<Breadcrumb.Item href="/" leadingIcon={<BellIcon />} trailingIcon={<FilterIcon />}>Home</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/documents" leadingIcon={<>D</>}>Documents</Breadcrumb.Item>
<Breadcrumb.Item href="/documents" leadingIcon={<FilterIcon />} trailingIcon={<ShoppingBagFilledIcon />}>Documents</Breadcrumb.Item>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/settings" leadingIcon={<>S</>}>Settings</Breadcrumb.Item>
<Breadcrumb.Item href="/settings" leadingIcon={<ShoppingBagFilledIcon />} trailingIcon={<BellIcon />}>Settings</Breadcrumb.Item>
</Breadcrumb>`
},
{
name: 'Only Icon',
code: `
<Breadcrumb>
<Breadcrumb.Item href="/" leadingIcon={<>H</>}/>
<Breadcrumb.Item href="/" leadingIcon={<BellIcon />}/>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/documents" leadingIcon={<>D</>}/>
<Breadcrumb.Item href="/documents" leadingIcon={<FilterIcon />}/>
<Breadcrumb.Separator/>
<Breadcrumb.Item href="/settings" leadingIcon={<>S</>}/>
<Breadcrumb.Item href="/settings" leadingIcon={<ShoppingBagFilledIcon />}/>
</Breadcrumb>`
}
]
Expand Down
36 changes: 32 additions & 4 deletions apps/www/src/content/docs/components/breadcrumb/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
iconsDemo,
ellipsisDemo,
dropdownDemo,
dropdownLinksDemo,
asDemo,
disabledDemo,
} from "./demo.ts";

<Demo data={playground} />
Expand Down Expand Up @@ -42,7 +44,25 @@ Groups all parts of the breadcrumb navigation.

### Item

Renders an individual breadcrumb link. Ref is forwarded to the rendered element when using `render` (not when using `dropdownItems`).
Renders an individual breadcrumb link. Use the `current` prop on the item that represents the current page so it is styled and exposed to assistive tech (e.g. `aria-current="page"`). Use the `disabled` prop for non-clickable, visually muted items (e.g. loading or no access).

Item elements expose data attributes for CSS state targeting so you can style current and disabled states without relying on internal class names:

| Attribute | When present |
|-----------|----------------|
| `data-current="true"` | Item is the current page (`current` prop) |
| `data-disabled="true"` | Item is disabled (`disabled` prop) |

Example:

```css
[data-current="true"] {
color: var(--my-brand);
}
[data-disabled="true"] {
opacity: 0.6;
}
```

<auto-type-table path="./props.ts" name="BreadcrumbItem" />

Expand Down Expand Up @@ -80,17 +100,18 @@ Use the `Breadcrumb.Ellipsis` component to truncate the breadcrumb trail when yo

### Icons

Breadcrumb items can include icons either alongside text or as standalone elements.
Breadcrumb items can include icons via `leadingIcon` (before the label) or `trailingIcon` (after the label), either alongside text or as standalone elements.

<Demo data={iconsDemo} />

### Dropdown

Breadcrumb items can include dropdown menus for additional navigation options. Specify the dropdown items using the `dropdownItems` prop.
Breadcrumb items can include dropdown menus for additional navigation options. Specify them with the `dropdownItems` prop: each entry is the same props as `<Menu.Item>` (e.g. `children` for the label, `onClick`, `disabled`, `render` for a link such as `<a href="…" />`, etc.). You can also pass `key` for stable list keys.

**Note:** When `dropdownItems` is provided, the `render` and `href` props are ignored.

<Demo data={dropdownDemo} />
<Demo data={dropdownLinksDemo} />

### Render

Expand All @@ -103,8 +124,15 @@ Use the `render` prop to render the breadcrumb item as a custom component. Pass

<Demo data={asDemo} />

### Disabled

Use the `disabled` prop for non-clickable, visually muted items—for example, loading states or segments the user does not have access to. Disabled items render as a span with `aria-disabled="true"` and do not navigate.

<Demo data={disabledDemo} />

## Accessibility

- Uses `nav` element with `aria-label="Breadcrumb"` for proper landmark identification
- Current page is indicated with `aria-current="page"`
- Separator elements are hidden from screen readers with `aria-hidden`
- Disabled items use `aria-disabled="true"`
- Separator elements are decorative and use `role="presentation"` and `aria-hidden="true"` so screen readers skip them
30 changes: 20 additions & 10 deletions apps/www/src/content/docs/components/breadcrumb/props.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactElement, ReactEventHandler, ReactNode } from 'react';
import { ReactElement, ReactNode } from 'react';

export interface BreadcrumbItem {
/** Text to display for the item */
Expand All @@ -7,28 +7,35 @@ export interface BreadcrumbItem {
/** URL for the item link */
href?: string;

/** Optional icon element to display */
/** Optional icon element to display before the label */
leadingIcon?: ReactNode;

/** Optional icon element to display after the label */
trailingIcon?: ReactNode;

/**
* Whether the item is the current page
* @defaultValue false
*/
current?: boolean;

/**
* Optional array of dropdown items
* When true, the item is non-clickable and visually muted (e.g. loading or no access).
* @defaultValue false
*/
disabled?: boolean;

/**
* Optional array of dropdown entries; each object is passed to `<Menu.Item>`
* (e.g. `children`, `onClick`, `render` for a link, etc.), plus optional `key`
* for React list reconciliation (not forwarded to `Menu.Item`).
*
* When `dropdownItems` is provided, the `render` and `href` props are ignored.
*/
dropdownItems?: {
/** Optional stable key for list reconciliation. Falls back to index if omitted. */
dropdownItems?: (Record<string, unknown> & {
key?: string;
/** Text to display for the dropdown item */
label: string;
/** Callback function when a dropdown item is clicked */
onClick?: ReactEventHandler<HTMLDivElement>;
}[];
children?: ReactNode;
})[];

/**
* Render prop for polymorphism (Base UI `useRender`).
Expand All @@ -37,6 +44,9 @@ export interface BreadcrumbItem {
* Example: `render={<NextLink />}`
*/
render?: ReactElement;

/** Custom CSS class name applied to the list item wrapper */
className?: string;
}

export interface BreadcrumbProps {
Expand Down
Loading
Loading