moduix-react
itembreadcrumbssource: ok · hit
Breadcrumbs
moduix React Breadcrumbs component.
npx shadcn add "https://moduix.dev/r/react/breadcrumbs.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightui-primitives@ark-ui/react
Registry dependencies
@moduix-react/a11y-labels@moduix-react/foundation@moduix-react/icons
Files3 · source cached 10/14/2026, 2:17:26 PM
src/components/breadcrumbs/Breadcrumbs.tsx
import type { HTMLArkProps } from '@ark-ui/react/factory';
import { ark } from '@ark-ui/react/factory';
import { clsx } from 'clsx';
import { Fragment, forwardRef, type ComponentRef, type ReactNode } from 'react';
import { a11yLabels } from '@/lib/moduix/a11yLabels';
import { ChevronRightIcon } from '@/lib/moduix/icons/ui';
import styles from './Breadcrumbs.module.css';
type BreadcrumbsPathLink = {
href: string;
label: ReactNode;
};
type BreadcrumbsPathProps = Omit<HTMLArkProps<'ol'>, 'asChild' | 'children'> & {
links: readonly BreadcrumbsPathLink[];
page: ReactNode;
separator?: ReactNode;
};
const Breadcrumbs = forwardRef<ComponentRef<typeof ark.nav>, HTMLArkProps<'nav'>>(
function Breadcrumbs(
{ className, 'aria-label': ariaLabel = a11yLabels.breadcrumb, ...props },
ref,
) {
return (
<ark.nav
ref={ref}
aria-label={ariaLabel}
{...props}
data-slot="breadcrumbs-root"
className={clsx(styles.root, className)}
/>
);
},
);
const BreadcrumbsList = forwardRef<ComponentRef<typeof ark.ol>, HTMLArkProps<'ol'>>(
function BreadcrumbsList({ className, ...props }, ref) {
return (
<ark.ol
ref={ref}
{...props}
data-slot="breadcrumbs-list"
className={clsx(styles.list, className)}
/>
);
},
);
const BreadcrumbsItem = forwardRef<ComponentRef<typeof ark.li>, HTMLArkProps<'li'>>(
function BreadcrumbsItem({ className, ...props }, ref) {
return (
<ark.li
ref={ref}
{...props}
data-slot="breadcrumbs-item"
className={clsx(styles.item, className)}
/>
);
},
);
const BreadcrumbsLink = forwardRef<ComponentRef<typeof ark.a>, HTMLArkProps<'a'>>(
function BreadcrumbsLink({ className, ...props }, ref) {
return (
<ark.a
ref={ref}
{...props}
data-slot="breadcrumbs-link"
className={clsx(styles.link, className)}
/>
);
},
);
const BreadcrumbsPage = forwardRef<ComponentRef<typeof ark.span>, HTMLArkProps<'span'>>(
function BreadcrumbsPage({ className, ...props }, ref) {
return (
<ark.span
ref={ref}
{...props}
data-slot="breadcrumbs-page"
aria-current="page"
className={clsx(styles.page, className)}
/>
);
},
);
const BreadcrumbsPath = forwardRef<ComponentRef<typeof ark.ol>, BreadcrumbsPathProps>(
function BreadcrumbsPath({ links, page, separator, ...props }, ref) {
return (
<BreadcrumbsList ref={ref} {...props}>
{links.map((link, index) => (
<Fragment key={index}>
<BreadcrumbsItem>
<BreadcrumbsLink href={link.href}>{link.label}</BreadcrumbsLink>
</BreadcrumbsItem>
<BreadcrumbsSeparator>{separator}</BreadcrumbsSeparator>
</Fragment>
))}
<BreadcrumbsItem>
<BreadcrumbsPage>{page}</BreadcrumbsPage>
</BreadcrumbsItem>
</BreadcrumbsList>
);
},
);
const BreadcrumbsSeparator = forwardRef<ComponentRef<typeof ark.li>, HTMLArkProps<'li'>>(
function BreadcrumbsSeparator({ className, children, ...props }, ref) {
return (
<ark.li
ref={ref}
{...props}
data-slot="breadcrumbs-separator"
aria-hidden="true"
className={clsx(styles.separator, className)}
>
{children ?? <ChevronRightIcon className={styles.separatorIcon} />}
</ark.li>
);
},
);
const BreadcrumbsEllipsis = forwardRef<ComponentRef<typeof ark.span>, HTMLArkProps<'span'>>(
function BreadcrumbsEllipsis({ className, children = '...', ...props }, ref) {
return (
<ark.span
ref={ref}
{...props}
data-slot="breadcrumbs-ellipsis"
aria-hidden="true"
className={clsx(styles.ellipsis, className)}
>
{children}
</ark.span>
);
},
);
export {
Breadcrumbs,
BreadcrumbsEllipsis,
BreadcrumbsItem,
BreadcrumbsLink,
BreadcrumbsList,
BreadcrumbsPage,
BreadcrumbsPath,
BreadcrumbsSeparator,
};src/components/breadcrumbs/Breadcrumbs.module.css
@layer moduix.components {
.root {
max-width: var(--moduix-breadcrumbs-max-width, 100%);
color: var(--moduix-breadcrumbs-color, var(--moduix-color-muted-foreground));
font-size: var(--moduix-breadcrumbs-font-size, var(--moduix-text-sm));
line-height: var(--moduix-breadcrumbs-line-height, var(--moduix-line-height-text-sm));
}
.list {
display: flex;
min-width: 0;
align-items: center;
gap: var(--moduix-breadcrumbs-gap, var(--moduix-spacing-1));
margin: 0;
padding: 0;
list-style: none;
white-space: nowrap;
> .item:last-child {
flex: 1 1 auto;
max-width: var(--moduix-breadcrumbs-item-max-width, 16rem);
}
}
.item {
display: inline-flex;
min-width: 0;
align-items: center;
}
.link,
.page {
display: block;
min-width: 0;
max-width: 100%;
padding-inline: var(--moduix-breadcrumbs-item-padding-x, var(--moduix-spacing-1));
white-space: nowrap;
}
.link,
.page,
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
}
.link {
color: var(--moduix-breadcrumbs-link-color, var(--moduix-color-muted-foreground));
text-decoration: var(--moduix-breadcrumbs-link-text-decoration, none);
text-underline-offset: var(--moduix-breadcrumbs-link-underline-offset, 0.2em);
border-radius: var(--moduix-breadcrumbs-link-radius, var(--moduix-radius-sm));
outline: none;
transition: color var(--moduix-breadcrumbs-link-transition, var(--moduix-transition-default));
@media (hover: hover) {
&:hover {
color: var(--moduix-breadcrumbs-link-color-hover, var(--moduix-color-foreground));
text-decoration: var(--moduix-breadcrumbs-link-text-decoration-hover, none);
}
}
&:focus-visible {
box-shadow: 0 0 0
var(
--moduix-breadcrumbs-focus-ring-width,
var(--moduix-focus-ring-inset-width, var(--moduix-border-width-sm))
)
var(--moduix-breadcrumbs-focus-ring-color, var(--moduix-color-ring));
}
}
.page {
color: var(--moduix-breadcrumbs-page-color, var(--moduix-color-foreground));
font-weight: var(--moduix-breadcrumbs-page-font-weight, var(--moduix-weight-medium));
}
.separator {
display: inline-flex;
align-items: center;
color: var(--moduix-breadcrumbs-separator-color, var(--moduix-color-muted-foreground));
font-size: var(--moduix-breadcrumbs-separator-font-size, 0.875em);
line-height: 1;
user-select: none;
}
.separatorIcon {
width: var(--moduix-breadcrumbs-separator-icon-size, 1em);
height: var(--moduix-breadcrumbs-separator-icon-size, 1em);
flex: none;
}
.separatorIcon:dir(rtl) {
transform: rotate(180deg);
}
.ellipsis {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: var(--moduix-breadcrumbs-ellipsis-size, var(--moduix-spacing-4));
min-height: var(--moduix-breadcrumbs-ellipsis-size, var(--moduix-spacing-4));
padding-inline: var(--moduix-breadcrumbs-item-padding-x, var(--moduix-spacing-1));
color: var(--moduix-breadcrumbs-ellipsis-color, var(--moduix-color-muted-foreground));
}
}src/components/breadcrumbs/index.ts
export * from './Breadcrumbs.js';