Skip to content
Open
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
4 changes: 4 additions & 0 deletions packages/admin-ui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Increase page header vertical padding. [#77152](https://github.com/WordPress/gutenberg/pull/77152)

### Internal

- `Breadcrumbs`: Migrate from `@wordpress/components` to `Link`, `Stack`, and `Text` from `@wordpress/ui`. [#77012](https://github.com/WordPress/gutenberg/pull/77012)

## 1.11.0 (2026-04-01)

### Bug Fixes
Expand Down
57 changes: 42 additions & 15 deletions packages/admin-ui/src/breadcrumbs/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
/**
* WordPress dependencies
*/
import { Link } from '@wordpress/route';
import { Link as RouterLink } from '@wordpress/route';
import { __ } from '@wordpress/i18n';
import {
__experimentalHeading as Heading,
__experimentalHStack as HStack,
} from '@wordpress/components';
import { Link, Stack, Text } from '@wordpress/ui';

/**
* Internal dependencies
Expand Down Expand Up @@ -54,28 +51,58 @@ export const Breadcrumbs = ( { items }: BreadcrumbsProps ) => {

return (
<nav aria-label={ __( 'Breadcrumbs' ) }>
<HStack
as="ul"
<Stack
render={ <ul /> }
direction="row"
align="center"
className="admin-ui-breadcrumbs__list"
spacing={ 0 }
justify="flex-start"
alignment="center"
>
{ precedingItems.map( ( item, index ) => (
<li key={ index }>
<Link to={ item.to }>{ item.label }</Link>
<Text
variant="body-lg"
render={
<Link
tone="neutral"
render={ <RouterLink to={ item.to } /> }
/>
}
>
{ item.label }
</Text>
Comment on lines +62 to +72
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we discussed or landed on consensus around Link component from @wordpress/route using Link from @wordpress/ui ?

(Something to address separately)

<Text
variant="body-lg"
aria-hidden="true"
className="admin-ui-breadcrumbs__separator"
>
/
</Text>
</li>
) ) }
<li>
{ lastItem.to ? (
<Link to={ lastItem.to }>{ lastItem.label }</Link>
<Text
variant="body-lg"
render={
<Link
tone="neutral"
render={ <RouterLink to={ lastItem.to } /> }
/>
}
>
{ lastItem.label }
</Text>
) : (
<Heading level={ 1 } truncate>
<Text
variant="heading-lg"
render={ <h1 /> }
className="admin-ui-breadcrumbs__current"
>
{ lastItem.label }
</Heading>
</Text>
) }
</li>
</HStack>
</Stack>
</nav>
);
};
Expand Down
24 changes: 9 additions & 15 deletions packages/admin-ui/src/breadcrumbs/style.scss
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
@use "@wordpress/base-styles/variables";

.admin-ui-breadcrumbs__list {
// @TODO: use Text component from UI when available.
font-family: var(--wpds-typography-font-family-heading);
font-size: var(--wpds-typography-font-size-lg);
font-weight: var(--wpds-typography-font-weight-medium);
line-height: var(--wpds-typography-line-height-lg);
list-style: none;
padding: 0;
margin: 0;
// gap prop don't seem to be working properly.
gap: 0;
min-height: 32px;

li:not(:last-child)::after {
content: "/";
margin: 0 variables.$grid-unit-10;
// @TODO: Remove truncation styles once `Text` supports truncation natively.
Comment thread
jameskoster marked this conversation as resolved.
.admin-ui-breadcrumbs__current {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

h1 {
font-size: inherit;
line-height: inherit;
.admin-ui-breadcrumbs__separator {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other libraries I typically see the separator as non-selectable text, either by SVG image or using user-select: none (example: MUI). This is also how it behaves in the current implementation before these changes (via ::after content behavior). It feels like it might be appropriate to disable user selection if it's largely decorative?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong feeling about this, but it makes sense to maintain the current behavior.

color: var(--wpds-color-stroke-surface-neutral);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figure this is okay as the separator is a decorative element and the list structure conveys meaning to assistive tech. I appreciate it's a bit of a gray area though. Thoughts welcome.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this affect hover styles and general click area of the item, though?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No because the separator is attached to the li not the a.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the recent changes, actually, I realized that the separator doesn't currently get the same typography styles as the individual items (the ::after currently inherits from li/ul, not from Text, so its font-family/font-size may not match the adjacent links)/

Maybe we could consider replacing the li::after pseudo-element separator with an explicit <Text> element? This would solve the typography inheritance concern .

Something like:

{ precedingItems.map( ( item, index ) => (
    <li key={ index }>
        <Text
            variant="body-lg"
            render={
                <Link
                    tone="neutral"
                    render={ <RouterLink to={ item.to } /> }
                />
            }
        >
            { item.label }
        </Text>
        <Text variant="body-lg" aria-hidden="true" className="admin-ui-breadcrumbs__separator">
            /
        </Text>
    </li>
) ) }

Since Text defaults to <span>, there's no need for a render prop — aria-hidden is passed directly. And because only precedingItems (all items except the last) are in this loop, the separator is never rendered after the current/last item.

This would let us remove the li:not(:last-child)::after CSS rule entirely and replace it with a simpler class:

.admin-ui-breadcrumbs__separator {
    color: var(--wpds-color-stroke-surface-neutral);
    margin: 0 var(--wpds-dimension-gap-sm);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, yes this makes sense!

margin: 0 var(--wpds-dimension-gap-sm);
user-select: none;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ ruleTester.run( 'use-recommended-components', rule, {

// Allowed @wordpress/ui components.
"import { Badge } from '@wordpress/ui';",
"import { Link } from '@wordpress/ui';",
"import { Stack } from '@wordpress/ui';",
"import { Badge, Stack } from '@wordpress/ui';",
"import { Text } from '@wordpress/ui';",
"import { Badge, Link, Stack, Text } from '@wordpress/ui';",
],

invalid: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/
const ALLOWLIST = {
'@wordpress/ui': {
allowed: [ 'Badge', 'Stack', 'Text' ],
allowed: [ 'Badge', 'Link', 'Stack', 'Text' ],
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this needs to be handled separately I understand :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct 😄 We can unleash these after #76783.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocked by #77044

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just flagging that Text is now allowed, and I guess Link may soon allowed too?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My expectation is that we're okay to make this allowed, but that we'd likely want to accompany this (separately, perhaps) with a more thorough reflection of its status like we did in #77044 (e.g. updating componentStatus of @wordpress/components ExternalLink to point to @wordpress/ui if appropriate).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to wait on merging this until Link is allowed in a separate PR if y'all prefer.

message:
'`{{ name }}` from `{{ source }}` is not yet recommended for use in a WordPress environment.',
},
Expand Down
Loading