radix-ui / primitives

Radix Primitives is an open-source UI component library for building high-quality, accessible design systems and web apps. Maintained by @workos.
https://radix-ui.com/primitives
MIT License
15.72k stars 814 forks source link

[New Primitive] `Breadcrumbs` #2050

Open jvzaniolo opened 1 year ago

jvzaniolo commented 1 year ago

Feature request

It would be nice to have an unstyled Breadcrumbs component in Radix UI.

Overview

Breadcrumbs are commonly used in websites but require some accessibility to work correctly with screen readers.

It's not a complex component to build, and it would add value to Radix since developers have to create their own Breadcrumbs or use another headless library like React Aria or Chakra UI.

Examples in other libraries

cr101 commented 1 year ago

Another example is the Mantine Breadcrumbs

junwen-k commented 1 year ago

Hi! I would love to see this component in Radix.

This is my API suggestion for Breadcrumb component for Radix :) Please feel free to comment your thoughts as well.

Ananomy

import * as Breadcrumb from "@radix-ui/react-breadcrumb";

export default () => (
  <Breadcrumb.Root>
    <Breadcrumb.List>
      <Breadcrumb.Item>
        <Breadcrumb.Link />
        <Breadcrumb.Separator />
      </Breadcrumb.Item>
      <Breadcrumb.Item>
        <Breadcrumb.Link />
        <Breadcrumb.Separator />
      </Breadcrumb.Item>
      <Breadcrumb.Item>
        <Breadcrumb.Link />
      </Breadcrumb.Item>
    </Breadcrumb.List>
  </Breadcrumb.Root>
);

HTML Output

<nav aria-label="breadcrumb">
  <ol>
    <li>
      <a></a>
      <span aria-hidden="true"></span>
    </li>
    <li>
      <a></a>
      <span aria-hidden="true"></span>
    </li>
    <li>
      <a aria-current="page" data-state="active"></a>
    </li>
  </ol>
</nav>

API Reference

Root

Prop Type Default
asChild boolean false
defaultValue string -
value string -
onValueChange function (value: string) => void -

List

Prop Type Default
asChild boolean false

Item

Prop Type Default
asChild boolean false

Link

Prop Type Default
asChild boolean false
disabled boolean false
value string -
Data attribute Values
[data-state] "active" \| "inactive"
[data-disabled] Present when disabled

Separator

Prop Type Default
asChild boolean false
tcolinpa commented 9 months ago

In terms of keyboard accessibility, what's the norm? Some questions here.

Functionality-wise, maybe a Breadcrumb.More can be created as well? One time I needed this and did the solution below using Popover component to render "what's inside" the ellipsis.

Screenshot 2024-02-01 at 20 59 30

export const BreadcrumbMore = ({
  children,
  element
}: BreadcrumbMoreProps) => (
  <li>
    <Popover>
      <PopoverTrigger>
        <span>
          {element ?? "..."}
        </span>
        <span
          aria-hidden="true"
          role="separator"
        >
          ›
        </span>
      </PopoverTrigger>
      <PopoverContent>
        {children}
      </PopoverContent>
    </Popover>
  </li>
); 
bragaru-i commented 7 months ago

Hi, any updates on this topic? Would be great to use this implementation over inheritance pattern for breadcrumbs

junwen-k commented 7 months ago

In terms of keyboard accessibility, what's the norm? Some questions here.

  • ArrowRight and ArrowLeft navigates among breadcrumb items? (React Aria doesn't support this, Chakra does);
  • Can Breadcrumb.Root be vertical-oriented? If so, then should ArrowUp and ArrowDown be used instead? (Actually, I've never seen a vertical breadcrumb, just wondering);
  • Tab focuses outside of Breadcrumb.Root to the next focusable element or to the next Breadcrumb.Item?;
  • Is the page current breadcrumb focusable? (For Chakra, it doesn't focus);

Functionality-wise, maybe a Breadcrumb.More can be created as well? One time I needed this and did the solution below using Popover component to render "what's inside" the ellipsis.

Screenshot 2024-02-01 at 20 59 30

export const BreadcrumbMore = ({
  children,
  element
}: BreadcrumbMoreProps) => (
  <li>
    <Popover>
      <PopoverTrigger>
        <span>
          {element ?? "..."}
        </span>
        <span
          aria-hidden="true"
          role="separator"
        >
          ›
        </span>
      </PopoverTrigger>
      <PopoverContent>
        {children}
      </PopoverContent>
    </Popover>
  </li>
); 

It seems like most popular libraries follows the same exact method (Tab to navigate to next breadcrumb link, and current page being unfocusable). However there seems to have no consensus.

It's also good to know that Shadcn recently added Breadcrumb component in which we could borrow some ideas from. I liked the way the components are structured, and I think the API suits Radix a lot.

import {
  Breadcrumb,
  BreadcrumbEllipsis,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
shimi-kuperli commented 2 months ago

+1