Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 6 additions & 0 deletions .changeset/witty-dryers-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@myst-theme/site": patch
"@myst-theme/book": patch
---

✨ Add navbar_end part in navbar UI
1 change: 1 addition & 0 deletions docs/_site/navbar_end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{button}`MyST Docs <https://mystmd.org>` {button}`GitHub <https://github.com/jupyter-book/myst-theme>`
1 change: 1 addition & 0 deletions docs/myst.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ project:
📢 **Announcement:** This is a test banner for the MyST Theme! [Learn more about MyST](/reference).
footer: _site/footer.md
primary_sidebar_footer: _site/primary_sidebar_footer.md
navbar_end: _site/navbar_end.md
toc:
- file: index.md
- file: ui.md
Expand Down
26 changes: 26 additions & 0 deletions docs/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ This page documents site-level UI components that appear on every page. use the
Many of the UI areas below allow users to insert custom content or interface elements using **parts**.
These are documented below, and in each case they use the same configuration pattern through `site.parts` or `project.parts` in `myst.yml`.

:::{note}
Parts are only supported in the **book** theme. The article theme does not yet support parts configuration.
:::

## Navigation bar

### Site logo
Expand Down Expand Up @@ -94,6 +98,28 @@ site:
- If the `.md` file it points to is empty, the footer will not be visible
- If not configured, falls back to the default "Made with MyST" footer

## Navbar End

Display custom content at the end of the top navigation bar, after the theme toggle button.
This is useful for adding icon links, badges, or call-to-action buttons to your navbar.

### Configuration

Create a markdown file with your navbar content and add it to `myst.yml`:

```yaml
site:
parts:
navbar_end: _site/navbar_end.md
```

### Behavior

- Renders inline at the end of the navbar (after the theme toggle and action buttons)
- Supports any MyST markdown content (links, images, formatting, etc.)
- Content is rendered inline, so keep it short - a few links or icons work best
- On narrow screens, the content moves into the primary sidebar menu

## Hiding Elements

Control the visibility of various page elements. All options can be set site-wide or per-page.
Expand Down
7 changes: 7 additions & 0 deletions packages/site/src/components/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PrimarySidebar } from './PrimarySidebar.js';
import type { Heading } from '@myst-theme/common';
import { getProjectHeadings } from '@myst-theme/common';
import type { SiteManifest } from 'myst-config';
import type { GenericParent } from 'myst-common';

/**
* PrimaryNavigation will load nav links and headers from the site manifest and display
Expand All @@ -15,13 +16,15 @@ export const PrimaryNavigation = ({
hide_toc,
mobileOnly,
footer,
navbarEnd,
}: {
children?: React.ReactNode;
projectSlug?: string;
sidebarRef?: React.RefObject<HTMLDivElement>;
hide_toc?: boolean;
mobileOnly?: boolean;
footer?: React.ReactNode;
navbarEnd?: GenericParent;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

GenericParent feels so... generic :)

Copy link
Copy Markdown
Contributor Author

@choldgraf choldgraf Mar 23, 2026

Choose a reason for hiding this comment

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

I can't tell if this is a suggestion to do something different or not - but it's a good point that footer already uses ReactNode....I can try to make this follow the same pattern and see if I can get the outcome we want. Is that what you'd like me to do?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sorry, I was musing to myself that the typing feels less than optimal here. But this requires a systematic review to address, nothing for you to take care of here!

}) => {
const config = useSiteManifest();
if (!config) return null;
Expand All @@ -41,6 +44,7 @@ export const PrimaryNavigation = ({
nav={nav}
headings={headings}
footer={footer}
navbarEnd={navbarEnd}
/>
);
};
Expand All @@ -63,6 +67,7 @@ export const ConfigurablePrimaryNavigation = ({
nav,
headings,
footer,
navbarEnd,
}: {
children?: React.ReactNode;
sidebarRef?: React.RefObject<HTMLDivElement>;
Expand All @@ -71,6 +76,7 @@ export const ConfigurablePrimaryNavigation = ({
nav?: SiteManifest['nav'];
headings?: Heading[];
footer?: React.ReactNode;
navbarEnd?: GenericParent;
}) => {
const [open, setOpen] = useNavOpen();
const top = useThemeTop();
Expand Down Expand Up @@ -102,6 +108,7 @@ export const ConfigurablePrimaryNavigation = ({
nav={nav}
headings={headings}
footer={footer}
navbarEnd={navbarEnd}
hide_toc={hide_toc}
mobileOnly={mobileOnly}
/>
Expand Down
14 changes: 14 additions & 0 deletions packages/site/src/components/Navigation/PrimarySidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useEffect, useRef } from 'react';
import classNames from 'classnames';
import { useNavigation } from '@remix-run/react';
import type { GenericParent } from 'myst-common';
import { MyST } from 'myst-to-react';
import {
useNavOpen,
useSiteManifest,
Expand Down Expand Up @@ -135,6 +137,7 @@ export const PrimarySidebar = ({
sidebarRef,
nav,
footer,
navbarEnd,
headings,
hide_toc,
mobileOnly,
Expand All @@ -143,6 +146,7 @@ export const PrimarySidebar = ({
nav?: SiteManifest['nav'];
headings?: Heading[];
footer?: React.ReactNode;
navbarEnd?: GenericParent;
hide_toc?: boolean;
mobileOnly?: boolean;
}) => {
Expand Down Expand Up @@ -204,6 +208,16 @@ export const PrimarySidebar = ({
<SidebarNav nav={nav} />
</nav>
)}
{navbarEnd && (
<div
className={classNames(
'article myst-primary-sidebar-navbar-end xl:hidden p-2 my-1 flex flex-wrap gap-2 [&_p]:contents',
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.

Here we show the navbar items up until we hit xl at which point it becomes hidden

sidebarSectionInsetClass,
)}
>
<MyST ast={navbarEnd} />
</div>
)}
{nav && headings && <div className="my-3 border-b-2 lg:hidden" />}
{headings && (
<nav
Expand Down
23 changes: 21 additions & 2 deletions packages/site/src/components/Navigation/TopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import classNames from 'classnames';
import { Menu, Transition } from '@headlessui/react';
import { ChevronDownIcon, Bars3Icon as MenuIcon } from '@heroicons/react/24/solid';
import type { SiteManifest, SiteNavItem } from 'myst-config';
import type { GenericParent } from 'myst-common';
import { MyST } from 'myst-to-react';
import { ThemeButton } from './ThemeButton.js';
import { Search } from './Search.js';
import {
Expand Down Expand Up @@ -113,7 +115,15 @@ export function NavItems({ nav }: { nav?: SiteManifest['nav'] }) {
);
}

export function TopNav({ hideToc, hideSearch }: { hideToc?: boolean; hideSearch?: boolean }) {
export function TopNav({
hideToc,
hideSearch,
navbarEnd,
}: {
hideToc?: boolean;
hideSearch?: boolean;
navbarEnd?: GenericParent;
}) {
const [open, setOpen] = useNavOpen();
const config = useSiteManifest();
const { title, nav, actions } = config ?? {};
Expand Down Expand Up @@ -152,8 +162,17 @@ export function TopNav({ hideToc, hideSearch }: { hideToc?: boolean; hideSearch?
<div className="flex items-center flex-grow w-auto">
<NavItems nav={nav} />
<div className="flex-grow block"></div>
{/* Search bar */}
{!hideSearch && <Search />}
<ThemeButton />
{/* Light/Dark theme button */}
<ThemeButton className="w-10 h-10 ml-3" />
{/* Custom part at end of navbar */}
Comment thread
choldgraf marked this conversation as resolved.
Outdated
{navbarEnd && (
<div className="article myst-navbar-end hidden xl:flex items-center ml-3 [&>*]:m-0">
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.

why hidden ?

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.

It's hidden because we have to render the navbar items in two places:

  • Once in the navbar for wide screens
  • Once in the sidebar drawer for narrow/mobile screens

So here we hide it until we hit xl width, at which point it becomes flex

<MyST ast={navbarEnd} />
</div>
)}
{/* Mobile pop-up for page actions */}
<div className="block sm:hidden">
<ActionMenu actions={actions} />
</div>
Expand Down
3 changes: 2 additions & 1 deletion themes/book/app/routes/$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,12 @@ function ArticlePageAndNavigationInternal({
<TabStateProvider>
{projectParts?.banner && <Banner content={projectParts.banner.mdast} />}
</TabStateProvider>
<TopNav hideToc={hide_toc} hideSearch={hideSearch} />
<TopNav hideToc={hide_toc} hideSearch={hideSearch} navbarEnd={projectParts?.navbar_end?.mdast} />
<PrimaryNavigation
sidebarRef={toc}
hide_toc={hide_toc}
footer={<SidebarFooter content={projectParts?.primary_sidebar_footer?.mdast} />}
navbarEnd={projectParts?.navbar_end?.mdast}
projectSlug={projectSlug}
/>
<TabStateProvider>
Expand Down
3 changes: 3 additions & 0 deletions themes/book/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ parts:
- id: primary_sidebar_footer
description: The primary sidebar footer, to replace Made with MyST
required: false
- id: navbar_end
description: Custom content displayed at the end of the top navbar
required: false
build:
install: npm ci --ignore-scripts
start: npm run start
Expand Down
Loading