docusaurus icon indicating copy to clipboard operation
docusaurus copied to clipboard

Add Breadcrumb customization options

Open ben-qnimble opened this issue 3 years ago • 18 comments

Have you read the Contributing Guidelines on issues?

Prerequisites

  • [X] I'm using the latest version of Docusaurus.
  • [X] I have tried the npm run clear or yarn clear command.
  • [X] I have tried rm -rf node_modules yarn.lock package-lock.json and re-installing packages.
  • [X] I have tried creating a repro with https://new.docusaurus.io.
  • [X] I have read the console error message carefully (if applicable).

Description

If you have the docs module configured where the structure matches the category index convention as mentioned for autogenerated sidebars, for example you have the following pages and URLs:

/docs/docs.md -> /docs/ /docs/item1.md -> /docs/item1 /docs/cat1/cat1.md -> /docs/cat1 /docs/cat1/sub1.md -> /docs/cat1/sub1 /docs/cat1/sub2.md -> /docs/cat1/sub2

it seems like in this usage, the breadcrumbs should match the URL structure and show all the links contained in the full URL. However, if you are on the sub2.md page, the bread crumbs goes from "/" to "/docs/cat1" and skip over the "/docs" url link:

"/" -> "/docs/cat1" -> "/docs/cat1/sub2"

I think the breadcrumb should either be

"/docs" -> "/docs/cat1" -> "/docs/cat1/sub2" or "/" -> "/docs" -> "/docs/cat1" -> "/docs/cat1/sub2"

Basically, the first idea would be to have the breadcrumbs "home" to go to "/docs" instead of "/" (when /docs is a valid URL). I think this makes sense since the breadcrumbs are specific to the docs module, so it isn't clear that "home" should go to the website home instead of the docs home. Maybe the way to implement this is for a parameter to set the breadcrumb home URL.

Another option would to (maybe optionally) add the "/docs" to the breadcrumb structure.

Reproducible demo

https://docusaurus.io/docs/sidebar/autogenerated

Steps to reproduce

Create structure like shown in https://docusaurus.io/docs/sidebar/autogenerated and enable breadcrumbs.

Expected behavior

breakcrumbs would look like

"/docs" -> "/docs/cat1" -> "/docs/cat1/sub2"

or

"/" -> "/docs" -> "/docs/cat1" -> "/docs/cat1/sub2"

Actual behavior

breadcrumbs are

"/" -> "/docs/cat1" -> "/docs/cat1/sub2"

Your environment

Tested on beta17

Self-service

  • [ ] I'd be willing to fix this bug myself.

ben-qnimble avatar Mar 21 '22 16:03 ben-qnimble

Hi, in a perfect world, this would be the case; however, this is virtually not achievable.

Routes are very arbitrary. Because you can use slug to customize a page's route, the existence of /docs/cat1/sub2 doesn't imply the existence of /docs or /docs/cat1 at all. And even if those routes exist, there's no implication that they are logically connected to /docs/cat1/sub2. Therefore, we would generate breadcrumbs based on the sidebar structure.

Now, maybe the issue is just a misconception of how breadcrumbs work, but being maximally charitable, this is actually what we plan to do: including more segments in the breadcrumbs. However, the problem is that the /docs path means the root path of a plugin, but plugins don't have human-readable labels yet. Moreover, we'd also like to include the sidebar in that, so that the breadcrumbs would be like plugin name > sidebar name > position of this doc in the sidebar. See https://github.com/facebook/docusaurus/discussions/6786.

Josh-Cena avatar Mar 21 '22 23:03 Josh-Cena

Thanks for the info, this is more complex than I appreciated. It seems like the discussions in #6786 may include changes that would address my issue. I've posted a comment there.

ben-qnimble avatar Mar 22 '22 14:03 ben-qnimble

As commented in the other issue, it looks like a plugin option such as:

breadcrumbPrefix: [
  { to: "/", label: "🏠" },
  { to: "/docs", label: "Docs home" }
];

could work for you?

Eventually, we could also add "breadcrumb item types", to cover more dynamic cases like showing a human-readable sidebar label?

breadcrumbPrefix: [
  { to: "/", label: "🏠" },
  { to: "/docs", label: "Docs home" },
  { type: "sidebarName" }
];

Does it make sense?

(note we don't have any human-readable docs-plugin label or sidebar label yet)


Some related questions:

  • Do we want to allow computing a custom breadcrumb on a per-doc basis, using a callback to create or modify the default breadcrumb?
  • Do we only want a prefix, or also a prefix? Should we have a { type: "sidebarBreadcrumb" } type for maximum flexibility?

slorber avatar Mar 23 '22 14:03 slorber

I don't think I fully appreciate what can be done if we have the option type: "sidebarName", but I like the breadcrumbPrefix idea in general.

I think having an individual doc override the breadcrumb makes sense. Something like:

---
id: MainDoc
slug: /
breadcrumbPrefix: [ { to: "/", label: "🏠" }]
---

I think this might be useful when the default breadcrumbPrefix is ["/", "/docs"] for the doc that has a slug of "/" (full url is "/docs") so the breadcrumb isn't "

/ -> /docs -> /docs

where the last item is the current page, which is also the last item in the breadcrumbPrefix. (Although maybe this could be address in other logic.)

ben-qnimble avatar Mar 23 '22 15:03 ben-qnimble

I don't think I fully appreciate what can be done if we have the option type: "sidebarName",

Docusaurus site has 2 sidebars: Docs and API

This means that one of those labels could eventually appear in the breadcrumb.

For example: Home > API > CLI instead of

image

adding frontMatter can be convenient yes.

Maybe for maximum flexibility you should be able to provide the full breadcrumb too?

breadcrumb: [ { to: "/", label: "🏠" } , {to:  "/docs/xyz", label :"XYZ" }]

breadcrumb:
  prefix: [ { to: "/", label: "🏠" }]

🤷‍♂️


I don't understand the last part 😅

slorber avatar Mar 23 '22 15:03 slorber

Thanks for the explanation. Having { type: "sidebarName" } as an option makes sense now. I think sites with multiple sidebars could use that functionality.

My point about an individual doc overriding the default breadcrumb prefix is that when on a page that is included in the default breadcrumb prefix, that might generate a breadcrumb that lists the page twice: once as part of the prefix, and once as the current page. Having the means to change the breadcrumb prefix for just that page might be a useful way to avoid that issue. (But again, there are other solutions).

I think having a breadcrumb prefix and a full breadcrumb override makes sense. One use case could be shortening the breadcrumbs (skipping some entries) for sites that have lots of structure. Or localizing it so you can't go 'too far' back in one shot.

ben-qnimble avatar Mar 23 '22 22:03 ben-qnimble

Hi guys, firstly, thanks for your contribution to the community, I really appreciate what you're doing and my team enjoys using Docusaurus.

Preface/context

We're currently using it to build an internal wiki website that would have several tech-stack scopes. Basically, the structure is similar like in @slorber's example. At the same time, we'd like to preserve the consistency within the docs/ directory. Docs structure would be similar to the following (example, similar to ours, in a way):

docs/
  frontend/
    frameworks/
      angular/
        best-practices.md
        whatever.md
  backend/
    python/
      best-practices.md
      whatever.md

We're using several autogenerated sidebars (each one represents a directory within docs/ folder). By using autogenerated + dirName in sidebars.js. I've also provided an example on stackblitz, consider checking it out.

It's all working fine except the breadcrumbs, it basically ignores the starting directory in docs/, since sidebar considers a directory from dirName to be a root. (e.g. for /docs/backend/python/best-practices/ we would get 🏠 > Python > Best Practices). This is not what we really need, and I'd like to propose a solution too.

As the guys suggested, we could use the breadcrumb prefix like: [{ to: "/", label: "🏠" } , {to: "/docs/xyz", label :"XYZ" }]. But, when leveraging autogenerated sidebar, it's going to be a pain to add this meta-header for each file within a directory (also, this would break the auto-generation purpose, in a way, since each child document would know where it lies).

So, my idea would be to also be able to use _category_.json (or some other meta file) for providing a prefix for all of its descendants. Here's an example:

docs/
  frontend/
    _category_.json <!-- { breadcrumbPrefix: [ { to: "/", label: "🏠" } , {to:  "/frontend", label :"Frontend" }] } -->
    frameworks/
      angular/
        best-practices.md
        whatever.md
  backend/
    _category_.json <!-- { breadcrumbPrefix: [ { to: "/", label: "🏠" } , {to:  "/backend", label :"Backend" }] } -->
    python/
      best-practices.md
      whatever.md

This way, a breacrumb prefix could be auto-generated for all of the pages within these directories. For /docs/frameworks/angular/best-practices it would be 🏠 > Frontend > Framework > Angular > Best Practices. Please note that best-practices.md doesn't have any information regarding the breadcrumb, it's inherited by one of the parent's _category_.json.

Also, so as not to duplicate the original root prefix, and to avoid the possible conflicts with it (in case it's changed in future), we could use some string constant, like "default_root" instead of { to: "/", label: "🏠" }. As a result, we would get something like breadcrumbsPrefix: ["default_root", {to: "/backend", label :"Backend" }]. Or, in case this is not going to work, we could also use some other header that would implicitly append our prefix to the root. Example: additionalBreadcrumbPrefix: [{to: "/backend", label :"Backend" }]

I'm interested to hear your opinion on this, guys. And thanks again!

chernodub avatar Mar 26 '22 03:03 chernodub

Hi chernodub,

I think I'm understanding what you want, and it seems to me, unless I'm missing something, that as currently structured the breadcrumbs will do what you want (except for the link to /docs). If you use the category index convention, and have:

docs/
  frontend/
    index.md   -- this is the page for the url "docs/frontend/"
    frameworks/
      index.md   -- this is the page for the url "docs/frontend/frameworks/"
      angular/
        index.md   -- this is the page for the url "docs/frontend/frameworks/angular/"
        best-practices.md
        whatever.md
  backend/
   index.md   -- this is the page for the url "docs/backend/"
    python/
      index.md   -- this is the page for the url "docs/backend/python/"
      best-practices.md
      whatever.md

then you will get an auto-generated breadcrumbs for /docs/backend/python/whatever of "Home" -> "Backend" -> "Python" -> "Whatever" and for /docs/frontend/frameworks/angular/whatever of "Home" -> "Frontend" -> "Frameworks" -> "Angular" -> "Whatever"

I think the only part that is missing is if you want "Home" -> "Docs" -> "Frontend" and/or to "Docs/" -> "Frontend" without the "Home"

Am I misunderstanding anything?

ben-qnimble avatar Mar 28 '22 16:03 ben-qnimble

Yes, you're right, that was my mistake, since I didn't know (at the time of writing my previous comment) that a root folder (specified in sidebars.js) is not considered to be a category in Docusaurus. So, _category_.json/index.md doesn't really fit here. Consider this example, root folders are not present in breadcrumb. What I need is to set a breadcrumbPrefix for each descendant of frontend/backend roots. I think it is only achievable either by providing some kind of root metadata, or by passing an additional property to autogenerated sidebar, like the following:

sidebars.js

const sidebars = {
  frontend: [{type: 'autogenerated', dirName: 'frontend', breadcrumb: 'Frontend'}],
  dotnet: [{type: 'autogenerated', dirName: 'dotnet', breadcrumb: 'Backend'}],
};

I still may miss something, so feel free to lead me to a right direction

chernodub avatar Mar 29 '22 10:03 chernodub

Autogenerated will be eventually unwrapped to regular sidebar items, so the idea is that we need to set metadata for each sidebar, like frontend: { breakdcrumb: "Frontend", items: [{ type: "autogenerated", dirName: "frontend"] } }, which is currently not possible (each sidebar is only an array of items; no place for metadata).

Josh-Cena avatar Mar 29 '22 10:03 Josh-Cena

Sounds reasonable, this will definitely fit a use-case I described before

chernodub avatar Mar 29 '22 10:03 chernodub

It would be great to have an option to provide some arbitrary breadcrumbs to Docusaurus.

Previously I used a custom breadcrumbs component for some pages of a Docusaurus website with a complex structure. But now there's a cute out-of-the-box component and I would like to enable it for the rest of the website. The point is that I cannot use a single template currently and have to copy it to ensure the components look indistinguishable.

Perhaps it would be acceptable to introduce something like sidebarItemsGenerator docs plugin option but for breadcrumbs?

kolos450 avatar May 08 '22 11:05 kolos450

just a breadcrumb_label option in doc front matter would be great

ezbeazy avatar Sep 04 '22 22:09 ezbeazy

frontMatter.breadcrumb_label + sidebarCategoryItem.breadcrumbLabel 👍 we can easily add this today until we figure more advanced customization cases.

Does anyone want to send a PR?

slorber avatar Sep 07 '22 10:09 slorber

I was trying to have the home breadcrumb link go to a custom URL (not /) and landed on this issue.

Solution

Eject

npm run swizzle @docusaurus/theme-classic DocBreadcrumbs -- --eject

Customize the URL

/src/theme/DocBreadcrumbs/index.js:

-  const homeHref = useBaseUrl('/');
+  const homeHref = useBaseUrl('/overview');

You should also update the aria labels in HomeBreadcrumbItem.


@slorber What if we extracted HomeBreadcrumbItem into a separate component that can be ejected without ejecting all the DocBreadcrumbs? I'd be happy to look into it and open a PR if it's useful 🙂

3v0k4 avatar Dec 12 '22 10:12 3v0k4

I'm not against extracting a few breadcrumbs components if that can simplify things on the short term until we figure out a good api/options.

We can keep the components as unsafe to swizzle until we find the best structure

slorber avatar Dec 14 '22 15:12 slorber